36 Commits
Author SHA1 Message Date
waiwaylee 80193c5e4c Merge branch 'feature' 2026-10-29 15:15:57 +08:00
waiwaylee fe7e194f18 更新:1、更新文档;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-29 15:15:47 +08:00
waiwaylee eb9f30c273 更新:1、修复航迹手动转TAS时,输出航迹更新信息时没有完整更新航迹信息的问题;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-29 14:43:35 +08:00
waiwaylee 0f3da9994c 更新:1、增加航迹指纹计算,TAS控制命令下发航迹指纹,不再下发批号,实现TAS正常回放;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-29 14:24:00 +08:00
waiwaylee 24ce1e6dbf 更新:1、修改日志组件,使用spdlog日志库;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-28 13:43:29 +08:00
waiwaylee 7e4cb2922d 更新:1、将参数配置信息对象改为全局可访问,并避免通过函数参数传入;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-27 15:56:02 +08:00
waiwaylee 76b0b460a5 更新:1、调整构建脚本,去除扫描模式配置项;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-27 14:48:16 +08:00
waiwaylee 019af7a229 更新:1、增加扫描模式配置项;
2、优化不必要的宏定义;
3、合并机扫和相扫版本;

Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-27 14:04:51 +08:00
waiwaylee 423c200659 更新:1、修改参数配置方式,改为由配置文件输入;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-27 10:36:14 +08:00
waiwaylee 0e325fb153 Merge branch 'master' into feature 2026-10-26 10:46:14 +08:00
waiwaylee a40b71bf57 更新:1、更新文档;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-26 10:45:25 +08:00
waiwaylee 3ecb210139 Merge branch 'feature' 2026-10-26 09:41:56 +08:00
waiwaylee 1ffb28770d 更新:1、增加航迹质量记录到数据库;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-22 11:21:58 +08:00
waiwaylee 693dbcbf89 Merge branch 'feature' 2026-10-22 10:56:08 +08:00
waiwaylee 44d3278cdc Merge branch 'fix' into feature 2026-10-22 10:55:03 +08:00
waiwaylee 6284f3f10a 更新:1、修改TAS模式下高度平滑点数;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-21 15:22:54 +08:00
waiwaylee 5d105d2076 更新:1、修改相扫模式下TAS航迹消亡圈数;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-20 17:35:38 +08:00
waiwaylee 17a3d998a6 更新:1、优化数据记录的性能,满足实时运行要求;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-20 17:31:50 +08:00
waiwaylee 3dee734d89 更新:1、优化配置构建脚本;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-20 13:40:39 +08:00
waiwaylee 2273841aa5 Merge branch 'master' into feature 2026-10-20 11:23:12 +08:00
waiwaylee 332f270b46 1、更新文档;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-20 11:22:33 +08:00
waiwaylee 3a9b1c4924 Merge branch 'feature' 2026-10-20 11:10:56 +08:00
waiwaylee 3088ce4910 更新:1、更新版本号至V1.5.8
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-20 11:10:45 +08:00
waiwaylee bd011d2434 Merge branch 'feature' 2026-10-20 11:08:41 +08:00
waiwaylee 782bff5e8e 更新:1、调整航迹质量下降逻辑;
2、普通航迹消亡条件改为由航迹质量确定;

Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-20 10:47:05 +08:00
waiwaylee 4b70b24515 更新:1、修改航迹质量下降策略,改为按点迹未更新时间计算;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-20 09:55:17 +08:00
waiwaylee d9a7bd0ee1 更新:1、优化调整临时航迹质量评估逻辑;
2、航迹起始时增加航迹质量门限;

Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-19 16:09:37 +08:00
waiwaylee 1ae0cf2512 更新:1、增加航迹质量信息输出;
2、航迹起始时计算初始航迹质量;
3、航迹关联时更新航迹质量;

Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-19 15:03:00 +08:00
waiwaylee 1b6cf62115 Merge branch 'master' into feature 2026-10-16 09:54:41 +08:00
waiwaylee 5f54d74ac3 Merge branch 'feature' 2026-10-14 09:25:02 +08:00
waiwaylee 3e6763c615 更新:1、更新扫描一圈判断条件,区分机扫和相扫不同的情况;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-14 09:24:49 +08:00
waiwaylee c01185bf9c Merge branch 'feature' 2026-10-13 14:50:10 +08:00
waiwaylee b4aed8a37b 更新:1、回调TWS外推时间差计算方法,避免TAS时同步出现TWS航迹的问题;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-13 14:49:48 +08:00
waiwaylee e98280409e 更新:1、调整临时航迹记录时机,在转为可靠航迹前记录,避免转为正式航迹后丢失当前帧的临时航迹记录;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-10 17:44:19 +08:00
waiwaylee 56b5449b9f 更新:1、修改TWS外推时,时间差计算方法,不再使用固定数据率计算时间差,而是使用最新时间戳来计算;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-09 14:04:22 +08:00
waiwaylee 5cb67ccef8 更新:1、增加TAS航迹信息、TAS关联信息记录到数据库;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-09 10:58:53 +08:00
167 changed files with 50218 additions and 1606 deletions
+3 -26
View File
@@ -12,38 +12,15 @@
"UNICODE",
"_UNICODE",
"NOMINMAX",
"DATA_PROCESS_CLASS_DLL_LIBRARY",
"PHASE_SCANNING"
"DATA_PROCESS_CLASS_DLL_LIBRARY"
],
"includePath": [
"${workspaceFolder}/data_process_class_dll",
"${workspaceFolder}/data_process_class_dll/Eigen",
"${workspaceFolder}/**"
],
"name": "X256_PS (MSVC2022 x64)",
"compileCommands": "${workspaceFolder}/build/X256_PS/compile_commands.json"
},
{
"compilerPath": "D:\\APP\\VisualStudio2022\\VC\\Tools\\MSVC\\14.44.35207\\bin\\Hostx64\\x64\\cl.exe",
"cStandard": "c17",
"cppStandard": "c++17",
"intelliSenseMode": "windows-msvc-x64",
"defines": [
"WIN32",
"_WINDOWS",
"UNICODE",
"_UNICODE",
"NOMINMAX",
"DATA_PROCESS_CLASS_DLL_LIBRARY",
"MECHANICAL_SCANNING"
],
"includePath": [
"${workspaceFolder}/data_process_class_dll",
"${workspaceFolder}/data_process_class_dll/Eigen",
"${workspaceFolder}/**"
],
"name": "X256_MS (MSVC2022 x64)",
"compileCommands": "${workspaceFolder}/build/X256_MS/compile_commands.json"
"name": "X256 (MSVC2022 x64)",
"compileCommands": "${workspaceFolder}/build/Debug/REC_ON/compile_commands.json"
}
]
}
+15 -10
View File
@@ -10,16 +10,21 @@ buildType:
long: Debug (MSVC2022 x64, C++17)
buildType: Debug
scanMode:
default: ps
recordMode:
default: REC_OFF
choices:
ps:
short: X256_PS
long: X256_PS - PHASE_SCANNING
REC_AUTO:
short: REC_AUTO
long: REC_AUTO - RDP_DATA_RECORD=AUTO (Debug 启用 / Release 关闭)
settings:
RADAR_SCAN_MODE: PHASE_SCANNING
ms:
short: X256_MS
long: X256_MS - MECHANICAL_SCANNING
RDP_DATA_RECORD: AUTO
REC_OFF:
short: REC_OFF
long: REC_OFF - RDP_DATA_RECORD=OFF (不编译记录模块)
settings:
RADAR_SCAN_MODE: MECHANICAL_SCANNING
RDP_DATA_RECORD: OFF
REC_ON:
short: REC_ON
long: REC_ON - RDP_DATA_RECORD=ON (强制启用记录模块)
settings:
RDP_DATA_RECORD: ON
+4 -4
View File
@@ -5,14 +5,14 @@
"name": "调试 DLL",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/build/X256_PS/bin/rdp_playback.exe",
"args": ["-d", "E:/Demo/QT/simianzhen256-v1.5.5-ms-vscode/build/X256_PS/bin/2026-07-18/15-34-32", "-i", "E:/Demo/QT/simianzhen256-v1.5.5-ms-vscode/build/X256_PS/bin/RadarConfigParam.ini", "-c", "E:/Demo/QT/simianzhen256-v1.5.5-ms-vscode/build/X256_PS/bin/2026-07-18/15-34-32/track.csv"],
"program": "${workspaceFolder}/build/Debug/REC_ON/bin/rdp_playback.exe",
"args": ["-d", "${workspaceFolder}/build/Debug/REC_ON/bin/2026-07-18/15-34-32", "-i", "${workspaceFolder}/build/Debug/REC_ON/bin/RadarConfigParam.ini", "-c", "${workspaceFolder}/build/Debug/REC_ON/bin/2026-07-18/15-34-32/track.csv"],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"preLaunchTask": "CMake: 配置并构建 X256_PS (Debug)",
"symbolSearchPath": "${workspaceFolder}/build/X256_PS/bin"
"preLaunchTask": "配置并构建(Debug/REC_ON)",
"symbolSearchPath": "${workspaceFolder}/build/Debug/REC_ON/bin"
},
{
"name": "附加到宿主进程(调试 DLL)",
+29 -9
View File
@@ -2,21 +2,41 @@
rem ===========================================================================
rem CMake build script (MSVC 2022 / v143, C++17, x64)
rem
rem Usage: build.bat <build-dir>
rem e.g. build.bat build\X256_PS
rem Usage:
rem build.bat ^<Debug^|Release^> [AUTO^|ON^|OFF]
rem
rem Examples:
rem build.bat Release OFF
rem build.bat Debug ON
rem
rem The build tree must have been configured by configure.bat first (same
rem configuration): build\<Debug^|Release^>\<REC_AUTO^|REC_OFF^|REC_ON^>
rem
rem Encoding: keep this file ASCII-only (see msvc_env.bat).
rem ===========================================================================
setlocal
set "BUILD_DIR=%~1"
if "%BUILD_DIR%"=="" (
echo Usage: build.bat ^<build-dir^>
exit /b 1
)
if "%~1"=="" goto :usage
call "%~dp0config_paths.bat" "%~1" "%~2"
if errorlevel 1 exit /b 1
rem Load the MSVC 2022 environment (vcvarsall.bat + cl.exe existence check).
call "%~dp0msvc_env.bat"
if errorlevel 1 exit /b %errorlevel%
if errorlevel 1 exit /b 1
pushd "%~dp0..\.."
if not exist "%BUILD_DIR%\CMakeCache.txt" (
echo [ERROR] "%BUILD_DIR%" is not configured - run configure.bat first
popd
exit /b 1
)
echo [build] %BUILD_DIR%
cmake --build "%BUILD_DIR%"
exit /b %errorlevel%
set "CMAKE_EXIT=%errorlevel%"
popd
exit /b %CMAKE_EXIT%
:usage
echo Usage: build.bat ^<Debug^|Release^> [AUTO^|ON^|OFF^]
exit /b 1
+49
View File
@@ -0,0 +1,49 @@
@echo off
rem ===========================================================================
rem Configure and build one build configuration (build type + record mode).
rem
rem Usage:
rem build_config.bat ^<Debug^|Release^> [AUTO^|ON^|OFF]
rem
rem Examples:
rem build_config.bat Release OFF
rem build_config.bat Debug ON
rem build_config.bat Release AUTO
rem
rem Runs configure.bat + build.bat for the same configuration:
rem build\<Debug^|Release^>\<REC_AUTO^|REC_OFF^|REC_ON^>
rem The mechanical-scanning and phase-scanning variants are merged into one
rem build; the scan mode is a runtime config item (TOML [radar_params] scan_mode).
rem The run stops at the first failure and returns non-zero.
rem
rem Encoding: keep this file ASCII-only (see msvc_env.bat).
rem ===========================================================================
setlocal EnableExtensions
set "BUILD_TYPE=%~1"
set "RECORD_MODE=%~2"
if not defined BUILD_TYPE goto :usage
if not defined RECORD_MODE set "RECORD_MODE=AUTO"
rem Validate + normalize the configuration once.
call "%~dp0config_paths.bat" "%BUILD_TYPE%" "%RECORD_MODE%"
if errorlevel 1 exit /b 1
echo.
echo [build_config] ==================== %BUILD_TYPE% / %RECORD_MODE% ====================
call "%~dp0configure.bat" "%BUILD_TYPE%" "%RECORD_MODE%"
if errorlevel 1 goto :failed
call "%~dp0build.bat" "%BUILD_TYPE%" "%RECORD_MODE%"
if errorlevel 1 goto :failed
echo.
echo [build_config] all done: %BUILD_TYPE% / %RECORD_MODE%
exit /b 0
:failed
echo.
echo [build_config] FAILED - stopped at the first failure, see the log above
exit /b 1
:usage
echo Usage: build_config.bat ^<Debug^|Release^> [AUTO^|ON^|OFF^]
exit /b 1
+33 -12
View File
@@ -1,22 +1,43 @@
@echo off
rem ===========================================================================
rem CMake clean script (MSVC 2022 / v143, C++17, x64)
rem CMake clean script - remove the build output of one configuration.
rem
rem Usage: clean.bat <build-dir>
rem e.g. clean.bat build\X256_PS
rem Usage:
rem clean.bat ^<Debug^|Release^> [AUTO^|ON^|OFF]
rem
rem Examples:
rem clean.bat Release OFF
rem clean.bat Debug ON
rem
rem The directory removed is the same one configure.bat/build.bat use:
rem build\<Debug^|Release^>\<REC_AUTO^|REC_OFF^|REC_ON^>
rem Use clean_all.bat to clean every configuration at once.
rem
rem Encoding: keep this file ASCII-only (see msvc_env.bat).
rem ===========================================================================
setlocal
set "BUILD_DIR=%~1"
if "%BUILD_DIR%"=="" (
echo Usage: clean.bat ^<build-dir^>
exit /b 1
if "%~1"=="" goto :usage
call "%~dp0config_paths.bat" "%~1" "%~2"
if errorlevel 1 exit /b 1
pushd "%~dp0..\.."
if not exist "%BUILD_DIR%" (
echo [clean] "%BUILD_DIR%" does not exist - nothing to do
popd
exit /b 0
)
rem Load the MSVC 2022 environment (vcvarsall.bat + cl.exe existence check).
call "%~dp0msvc_env.bat"
if errorlevel 1 exit /b %errorlevel%
echo [clean] remove "%BUILD_DIR%"
rmdir /s /q "%BUILD_DIR%"
if exist "%BUILD_DIR%" (
echo [ERROR] cannot remove "%BUILD_DIR%", close open files and retry.
popd
exit /b 1
)
popd
exit /b 0
cmake --build "%BUILD_DIR%" --target clean
exit /b %errorlevel%
:usage
echo Usage: clean.bat ^<Debug^|Release^> [AUTO^|ON^|OFF^]
exit /b 1
+54
View File
@@ -0,0 +1,54 @@
@echo off
rem ===========================================================================
rem Remove the build output of every build configuration.
rem
rem Usage:
rem clean_all.bat
rem
rem Build layout (see config_paths.bat):
rem build\<Debug^|Release^>\<REC_AUTO^|REC_OFF^|REC_ON^>
rem
rem The legacy per-scan-mode roots build\X256_PS and build\X256_MS (used before
rem the mechanical/phase variants were merged) are removed as a whole as well.
rem build\output (archived package export) is never touched.
rem
rem Encoding: keep this file ASCII-only (see msvc_env.bat).
rem ===========================================================================
setlocal EnableExtensions
pushd "%~dp0..\.."
set "FAILED="
rem legacy layout: one root per scan mode (contains its own build tree directly)
for %%S in (X256_PS X256_MS) do (
call :remove "build\%%S"
)
rem current layout: build\<Debug|Release>\<REC_*>
for %%T in (Debug Release) do (
for %%R in (REC_AUTO REC_OFF REC_ON) do (
call :remove "build\%%T\%%R"
)
rem drop the build type root when every configuration is gone
rmdir "build\%%T" 2>nul
)
echo.
echo [clean_all] archived package export "build\output" left untouched
if defined FAILED (
echo [clean_all] FAILED - some directories could not be removed, close open files and retry
popd
exit /b 1
)
echo [clean_all] all build configurations removed
popd
exit /b 0
:remove
if not exist "%~1" exit /b 0
echo [clean_all] remove "%~1"
rmdir /s /q "%~1"
if exist "%~1" (
echo [ERROR] cannot remove "%~1"
set "FAILED=1"
)
exit /b 0
+57
View File
@@ -0,0 +1,57 @@
@echo off
rem ===========================================================================
rem Derive the build directory of one build configuration.
rem
rem Internal helper, called by configure.bat / build.bat / clean.bat /
rem build_config.bat as:
rem call config_paths.bat ^<Debug^|Release^> [AUTO^|ON^|OFF]
rem
rem The mechanical-scanning and phase-scanning variants are merged into one
rem build since V1.5.9, so a configuration is only build type + record mode.
rem The scan mode is a runtime config item (TOML [radar_params] scan_mode).
rem
rem On success the following variables are exported into the caller scope:
rem BUILD_TYPE Debug / Release
rem RECORD_MODE AUTO / ON / OFF
rem REC_TAG REC_AUTO / REC_OFF / REC_ON
rem BUILD_DIR build\<BUILD_TYPE>\<REC_TAG>
rem
rem Exit code: 0 = ok, 1 = invalid configuration.
rem
rem Encoding: keep this file ASCII-only (see msvc_env.bat).
rem ===========================================================================
set "BUILD_TYPE="
set "RECORD_MODE="
set "REC_TAG="
set "BUILD_DIR="
if "%~1"=="" goto :usage
if /i "%~1"=="Debug" set "BUILD_TYPE=Debug"
if /i "%~1"=="Release" set "BUILD_TYPE=Release"
if not defined BUILD_TYPE (
echo [ERROR] invalid build type: "%~1"
echo expected Debug or Release
exit /b 1
)
if not "%~2"=="" set "RECORD_MODE=%~2"
if not defined RECORD_MODE set "RECORD_MODE=AUTO"
if /i "%RECORD_MODE%"=="AUTO" set "RECORD_MODE=AUTO"
if /i "%RECORD_MODE%"=="ON" set "RECORD_MODE=ON"
if /i "%RECORD_MODE%"=="OFF" set "RECORD_MODE=OFF"
if "%RECORD_MODE%"=="AUTO" set "REC_TAG=REC_AUTO"
if "%RECORD_MODE%"=="ON" set "REC_TAG=REC_ON"
if "%RECORD_MODE%"=="OFF" set "REC_TAG=REC_OFF"
if not defined REC_TAG (
echo [ERROR] invalid record mode: "%~2"
echo expected AUTO or ON or OFF
exit /b 1
)
set "BUILD_DIR=build\%BUILD_TYPE%\%REC_TAG%"
exit /b 0
:usage
echo Usage: config_paths.bat ^<Debug^|Release^> [AUTO^|ON^|OFF^]
exit /b 1
+40 -39
View File
@@ -1,73 +1,74 @@
@echo off
rem ===========================================================================
rem CMake configure script (MSVC 2022 / v143, C++17, x64)
rem CMake configure script (MSVC 2022 / v143, C++17, x64, NMake Makefiles)
rem
rem Usage: configure.bat <build-dir> <PHASE_SCANNING^|MECHANICAL_SCANNING> [Debug^|Release] [AUTO^|ON^|OFF]
rem e.g. configure.bat build\X256_PS PHASE_SCANNING Release
rem configure.bat build\X256_PS Debug ON (Debug + data recording enabled)
rem Usage:
rem configure.bat ^<Debug^|Release^> [AUTO^|ON^|OFF]
rem
rem The 4th argument selects the optional data-recording debug feature
rem Examples:
rem configure.bat Release OFF
rem configure.bat Debug ON
rem
rem The build directory is derived from the configuration (see
rem config_paths.bat): build\<Debug^|Release^>\<REC_AUTO^|REC_OFF^|REC_ON^>
rem The mechanical-scanning and phase-scanning variants are merged into one
rem build; the scan mode is a runtime config item (TOML [radar_params] scan_mode).
rem
rem The 2nd argument selects the optional data-recording debug feature
rem (CMake cache variable RDP_DATA_RECORD). When it is omitted, CMake's AUTO
rem default applies: Debug = enabled, Release = disabled. See
rem docs/data_record_debug.md.
rem default applies: Debug = enabled, Release = disabled.
rem See docs/data_record_debug.md.
rem
rem The generator stays "NMake Makefiles" (the choice made during the VS Code
rem migration); cl.exe is pinned to the MSVC 2022 x64 toolset.
rem
rem A build directory that was configured by an older compiler (for example the
rem MSVC 2013 cache) is removed first: neither its CMake cache nor its object
rem files can be reused across a compiler upgrade.
rem A build directory that was configured by an older compiler (for example
rem the MSVC 2013 cache) is removed first: neither its CMake cache nor its
rem object files can be reused across a compiler upgrade.
rem
rem Encoding: keep this file ASCII-only (see msvc_env.bat).
rem ===========================================================================
setlocal
set "BUILD_DIR=%~1"
set "SCAN_MODE=%~2"
set "BUILD_TYPE=%~3"
set "RECORD_MODE=%~4"
if "%BUILD_DIR%"=="" (
echo Usage: configure.bat ^<build-dir^> ^<PHASE_SCANNING^|MECHANICAL_SCANNING^> [Debug^|Release] [AUTO^|ON^|OFF]
exit /b 1
)
if "%SCAN_MODE%"=="" (
echo Usage: configure.bat ^<build-dir^> ^<PHASE_SCANNING^|MECHANICAL_SCANNING^> [Debug^|Release] [AUTO^|ON^|OFF]
exit /b 1
)
if "%BUILD_TYPE%"=="" set "BUILD_TYPE=Release"
if "%RECORD_MODE%"=="" set "RECORD_MODE=AUTO"
if "%~1"=="" goto :usage
call "%~dp0config_paths.bat" "%~1" "%~2"
if errorlevel 1 exit /b 1
echo [configure] build dir : %BUILD_DIR%
echo [configure] settings : %BUILD_TYPE% / RDP_DATA_RECORD=%RECORD_MODE%
rem Load the MSVC 2022 environment (vcvarsall.bat + cl.exe existence check).
call "%~dp0msvc_env.bat"
if errorlevel 1 exit /b %errorlevel%
if errorlevel 1 exit /b 1
rem Drop a build tree left behind by another toolset.
if not exist "%BUILD_DIR%\CMakeCache.txt" goto :configure
pushd "%~dp0..\.."
if not exist "%BUILD_DIR%\CMakeCache.txt" goto :do_configure
set "OLD_TOOLSET="
if exist "%BUILD_DIR%\.msvc_toolset" set /p OLD_TOOLSET=<"%BUILD_DIR%\.msvc_toolset"
if /i "%OLD_TOOLSET%"=="%MSVC_TOOLSET_VERSION%" goto :configure
if /i "%OLD_TOOLSET%"=="%MSVC_TOOLSET_VERSION%" goto :do_configure
echo [configure] "%BUILD_DIR%" was configured with MSVC toolset "%OLD_TOOLSET%" - rebuilding from scratch
rmdir /s /q "%BUILD_DIR%"
if exist "%BUILD_DIR%" (
echo [ERROR] cannot remove "%BUILD_DIR%", please delete it manually and retry.
popd
exit /b 1
)
:configure
:do_configure
echo [configure] compiler : %MSVC_CXX_COMPILER%
echo [configure] build dir: %BUILD_DIR%
echo [configure] mode : %SCAN_MODE% / %BUILD_TYPE%
echo [configure] record : %RECORD_MODE% (RDP_DATA_RECORD: AUTO/ON/OFF)
pushd "%~dp0..\.."
cmake -S . -B "%BUILD_DIR%" -G "NMake Makefiles" ^
-DCMAKE_BUILD_TYPE=%BUILD_TYPE% ^
-DRADAR_SCAN_MODE=%SCAN_MODE% ^
-DRDP_DATA_RECORD=%RECORD_MODE% ^
-DCMAKE_CXX_COMPILER="%MSVC_CXX_COMPILER%"
set "CMAKE_EXIT=%errorlevel%"
if not "%CMAKE_EXIT%"=="0" (
popd
if not "%CMAKE_EXIT%"=="0" exit /b %CMAKE_EXIT%
exit /b %CMAKE_EXIT%
)
rem Remember which toolset produced this build tree (checked on the next run).
> "%BUILD_DIR%\.msvc_toolset" echo %MSVC_TOOLSET_VERSION%
popd
exit /b 0
:usage
echo Usage: configure.bat ^<Debug^|Release^> [AUTO^|ON^|OFF^]
exit /b 1
+2 -2
View File
@@ -1,7 +1,7 @@
{
"cmake.configureOnOpen": false,
"cmake.generator": "NMake Makefiles",
"cmake.buildDirectory": "${workspaceFolder}/build/X256_PS",
"cmake.buildDirectory": "${workspaceFolder}/build/${buildType}/${variant:recordMode}",
"cmake.configureArgs": [
"-DCMAKE_CXX_COMPILER=D:/APP/VisualStudio2022/VC/Tools/MSVC/14.44.35207/bin/Hostx64/x64/cl.exe"
],
@@ -12,5 +12,5 @@
},
"C_Cpp.default.cppStandard": "c++17",
"C_Cpp.default.intelliSenseMode": "windows-msvc-x64",
"C_Cpp.default.compileCommands": "${workspaceFolder}/build/X256_PS/compile_commands.json"
"C_Cpp.default.compileCommands": "${workspaceFolder}/build/Debug/REC_ON/compile_commands.json"
}
+62 -137
View File
@@ -5,166 +5,91 @@
},
"tasks": [
{
"label": "CMake: 配置 X256_PS (Release, MSVC2022 x64)",
"label": "清理全部",
"detail": "删除 build/<Debug|Release>/<REC_*> 下所有配置的构建输出(含旧版 build/X256_PS、build/X256_MS;build/output 打包留档不动)",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\configure.bat",
"args": [
"${workspaceFolder}/build/X256_PS",
"PHASE_SCANNING",
"Release"
],
"problemMatcher": []
"command": "${workspaceFolder}\\.vscode\\scripts\\clean_all.bat",
"problemMatcher": [],
"presentation": {
"reveal": "always",
"panel": "shared",
"clear": true
}
},
{
"label": "CMake: 配置 X256_PS (Debug, MSVC2022 x64)",
"label": "配置并构建(Release/REC_OFF)",
"detail": "Release + RDP_DATA_RECORD=OFF(机扫/相扫已合并为同一版本,单次构建一个配置)",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\configure.bat",
"command": "${workspaceFolder}\\.vscode\\scripts\\build_config.bat",
"args": [
"${workspaceFolder}/build/X256_PS",
"PHASE_SCANNING",
"Debug"
],
"problemMatcher": []
},
{
"label": "CMake: 构建 X256_PS (Release)",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\build.bat",
"args": [
"${workspaceFolder}/build/X256_PS"
],
"group": "build",
"problemMatcher": [
"$msCompile"
]
},
{
"label": "CMake: 构建 X256_PS (Debug)",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\build.bat",
"args": [
"${workspaceFolder}/build/X256_PS"
],
"group": "build",
"problemMatcher": [
"$msCompile"
]
},
{
"label": "CMake: 清理 X256_PS",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\clean.bat",
"args": [
"${workspaceFolder}/build/X256_PS"
],
"problemMatcher": []
},
{
"label": "CMake: 配置 X256_MS (Release, MSVC2022 x64)",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\configure.bat",
"args": [
"${workspaceFolder}/build/X256_MS",
"MECHANICAL_SCANNING",
"Release"
],
"problemMatcher": []
},
{
"label": "CMake: 配置 X256_MS (Debug, MSVC2022 x64)",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\configure.bat",
"args": [
"${workspaceFolder}/build/X256_MS",
"MECHANICAL_SCANNING",
"Debug"
],
"problemMatcher": []
},
{
"label": "CMake: 构建 X256_MS (Release)",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\build.bat",
"args": [
"${workspaceFolder}/build/X256_MS"
],
"group": "build",
"problemMatcher": [
"$msCompile"
]
},
{
"label": "CMake: 构建 X256_MS (Debug)",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\build.bat",
"args": [
"${workspaceFolder}/build/X256_MS"
],
"group": "build",
"problemMatcher": [
"$msCompile"
]
},
{
"label": "CMake: 清理 X256_MS",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\clean.bat",
"args": [
"${workspaceFolder}/build/X256_MS"
],
"problemMatcher": []
},
{
"label": "CMake: 配置并构建 X256_PS (Release)",
"dependsOrder": "sequence",
"dependsOn": [
"CMake: 配置 X256_PS (Release, MSVC2022 x64)",
"CMake: 构建 X256_PS (Release)"
"Release",
"OFF"
],
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": []
"problemMatcher": [
"$msCompile"
],
"presentation": {
"reveal": "always",
"panel": "shared"
}
},
{
"label": "CMake: 配置并构建 X256_PS (Debug)",
"dependsOrder": "sequence",
"dependsOn": [
"CMake: 配置 X256_PS (Debug, MSVC2022 x64)",
"CMake: 构建 X256_PS (Debug)"
"label": "配置并构建(Release/REC_ON)",
"detail": "Release + RDP_DATA_RECORD=ON(机扫/相扫已合并为同一版本,单次构建一个配置)",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\build_config.bat",
"args": [
"Release",
"ON"
],
"group": "build",
"problemMatcher": []
"problemMatcher": [
"$msCompile"
],
"presentation": {
"reveal": "always",
"panel": "shared"
}
},
{
"label": "CMake: 配置并构建 X256_MS (Release)",
"dependsOrder": "sequence",
"dependsOn": [
"CMake: 配置 X256_MS (Release, MSVC2022 x64)",
"CMake: 构建 X256_MS (Release)"
"label": "配置并构建(Debug/REC_OFF)",
"detail": "Debug + RDP_DATA_RECORD=OFF(机扫/相扫已合并为同一版本,单次构建一个配置)",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\build_config.bat",
"args": [
"Debug",
"OFF"
],
"group": "build",
"problemMatcher": []
"problemMatcher": [
"$msCompile"
],
"presentation": {
"reveal": "always",
"panel": "shared"
}
},
{
"label": "CMake: 配置并构建 X256_MS (Debug)",
"dependsOrder": "sequence",
"dependsOn": [
"CMake: 配置 X256_MS (Debug, MSVC2022 x64)",
"CMake: 构建 X256_MS (Debug)"
"label": "配置并构建(Debug/REC_ON)",
"detail": "Debug + RDP_DATA_RECORD=ON(机扫/相扫已合并为同一版本,单次构建一个配置)",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\build_config.bat",
"args": [
"Debug",
"ON"
],
"group": "build",
"problemMatcher": []
},
{
"label": "CMake: 配置并构建全部 (X256_PS + X256_MS, Release)",
"dependsOrder": "sequence",
"dependsOn": [
"CMake: 配置并构建 X256_PS (Release)",
"CMake: 配置并构建 X256_MS (Release)"
"problemMatcher": [
"$msCompile"
],
"problemMatcher": []
"presentation": {
"reveal": "always",
"panel": "shared"
}
}
]
}
+112 -2
View File
@@ -15,6 +15,116 @@
### Removed
## [1.5.9] - 2026-10-29
> 本次发布把 1.5.8 之后的全部改动(提交 423c200 … eb9f30c)归档:参数配置方式改造 + 机扫/相扫合并为单一份构建、工作参数改为全局对象、日志组件改为 spdlog、构建脚本与 VS Code 任务适配、**航迹指纹**与手动转 TAS 输出修复。
> 版本号:`parameters.h` 的 `PATCH_VERSION` = 9、`VERSION_INFO` = `"GENERIC"`(合并后无体制后缀),运行时 `version_info` 为 **`V1.5.9.GENERIC`**(版本号于 019af7a 提升)。
### Added
- **航迹指纹(0f3da99)**:`Trust_Track` 新增 `int Track_fingerprint`(16 bit),起批转可靠航迹时用**末点**的 `r` / `azi` / `vr` / `RCS` / `CPI_time` 计算(`track_init.cpp`:`quantize` 量化到 0.01 m / 0.001° / 0.01 m·s⁻¹ / 0.1 dBsm,随后 splitmix64 混合并 `fold16` 折叠);TAS 波束输出的 `TrackingBeam::TAS_track_index` 改为下发**指纹**(不再下发可复用的航迹批号),TAS 数据帧的 `DataRev::TAS_track_index` 也被解释为指纹;`track_process(model=3)` 按指纹在航迹表中定位航迹(命中后置 `manual_tracking_flag=1`、`Track_Mode=1`)再做关联与外推。目的是让 TAS 控制命令在回放/重连后仍能唯一对应同一目标(批号会复用,指纹由目标特征生成)。
- **内置 spdlog v1.17.0 并重写日志模块(日志组件 spdlog 改造,24ce1e6)**:对应 `requirements.md` 三条需求——① 日志同时输出到标准输出与日志文件(文件仍为 `RDP_LOG_DIR` = `.\rdp_logs\`,文件名按日期+时间命名、每次启动一个文件);② 原 `RDP_LOG` 流式宏按 spdlog 级别拆为 `RDP_LOG_TRACE`/`RDP_LOG_DEBUG`/`RDP_LOG_INFO`/`RDP_LOG_WARN`/`RDP_LOG_ERROR`/`RDP_LOG_CRITICAL`(另提供 `RDP_LOG_ERR` 作为 `RDP_LOG_ERROR` 别名,对应 spdlog 的 `err` 级别命名),调用改为 fmt 风格,如 `RDP_LOG_INFO("parameters_reload ok: {}", path)`;③ 原 `RDP_LOG_ENABLE` 宏删除,改用 spdlog 的 `SPDLOG_ACTIVE_LEVEL` 在编译期控制日志级别。实现要点:新增内置第三方源码 `third_party/spdlog`(v1.17.0,commit `79524ddd08a4ec981b7fea76afd08ee05f83755d`,MIT;含内置 {fmt} 12.1.0;`include/` 105 文件 + `src/` 7 文件 + LICENSE,来源/复现/升级见 `third_party/spdlog/UPSTREAM.md`);`CMakeLists.txt` 新增静态库目标 `spdlog`(`SPDLOG_COMPILED_LIB`,第三方源码以 `/w` 编译,头文件目录与 `SPDLOG_COMPILED_LIB`/`SPDLOG_ACTIVE_LEVEL` 经 PUBLIC 接口同时提供给 `data_process_class_dll` 与 `rdp_record` 两个目标,保证跨 C++17/C++20 配置一致);新增 CMake 缓存变量 `RDP_LOG_LEVEL`(AUTO/TRACE/DEBUG/INFO/WARN/ERROR/CRITICAL/OFF,默认 AUTO = Debug 构建 TRACE、其余构建 INFO;OFF = 编译期关闭全部日志,`rdp_log::Init()` 也不再创建日志文件;非法值在 configure 阶段报错);`rdp_log.h/.cpp` 重写为 spdlog 双 sink(`stdout_sink_mt` + `basic_file_sink_mt`)默认 logger,日志行格式为 `[YYYY-MM-DD HH:MM:SS.mmm] [级别] [文件名:行号] 内容`(级别按 spdlog 默认名输出 trace/debug/info/warning/error/critical),`flush_on(trace)` 逐条落盘,日志文件创建失败时降级为仅标准输出并返回 false(不中断雷达处理),spdlog 内部错误经 error handler 写标准错误;日志文件命名沿用旧风格 `rdp_YYYYMMDD_HHMMSS.log`,同一秒内文件已存在(Destroy→GetB 重连、同目录多进程启动)时追加 `_1`~`_999` 后缀、不复用同一文件;新增内部辅助 `rdp_log::log_file_path()`。
- 对应本次日志改造的文档:新增 `third_party/spdlog/UPSTREAM.md`(内置 spdlog 的来源/版本/许可证/复现与升级约束);`README.md` 与 `docs/` 按源码同步(见 Fixed 中文档同步条目)。
- **新增配置参数 `scan_mode`(参数配置方式改造)**:`struct.h::RDP_parameter` 的雷达工作参数组新增 `int scan_mode`(紧跟 `radar_work_mode`),语义 **0=相扫模式 / 1=机扫模式**;`rdp_parameter_loader.cpp` 在 `[radar_params]` 登记该键为**必填 int**,并校验只允许 0/1;样例 `config/config_tmp.toml` 新增 `scan_mode = 0`(相扫)。该配置项决定 `data_preprocess()` 的 TWS 判圈逻辑(见 Changed)。
- **参数加载器(参数配置方式改造,423c200)**:新增内部模块 `data_process_class_dll/rdp_parameter_loader.h/.cpp`,把 TOML 配置文件解析到 `struct.h::RDP_parameter`(接口 `int rdp_parameter_load(const char* path, RDP_parameter& parameter, std::string* error_msg)`)。采用**严格校验**:11 个配置段(`radar_params` / `cohesion` / `track_associate` / `track_init` / `track_die` / `track_quality` / `track_index` / `beam_control` / `singer_model` / `track_init_prohibite_area` / `tas_prohibite_area`)全部必填;标量键全部必填且校验类型(数字/整数)与取值范围;拒绝未知配置段与未知键(拼错即报错);屏蔽区 4 个数组必须存在且元素个数等于对应 `*_num`(`*_num` 限 0~30,为 0 时数组可省略或为空数组),并逐区校验距离/方位范围。任一错误返回 -1 并给出首条可读原因,**不修改**调用方传入的参数(全部通过后才一次性赋值)。
- **新增公开虚接口 `parameters_reload`(参数配置方式改造)**:`data_process_class_dll.h` 的 `Data_process_class_dll` 新增 `virtual int parameters_reload(const char* parameter_file_path)`(基类空实现返回 -1);`Data_Process` 实现为「加载成功后整体赋值 `rdp_parameter`,失败保留当前参数并经 `RDP_LOG` 打印原因」。宿主改为在 `GetB()` 之后调用一次即可完成全部工作参数配置,重连/换配置时再次调用可热重载;不再需要构造并下发 `struct RadarPara`。
- **示例配置文件 `config/config_tmp.toml`(参数配置方式改造)**:11 个配置段与 `RDP_parameter` 的注释分组一一对应,键名与结构体成员名完全一致(`[radar_params]`、`[cohesion]`、`[track_associate]`、`[track_init]`、`[track_die]`、`[track_quality]`、`[track_index]`、`[beam_control]`、`[singer_model]` 为标量段,两个屏蔽区段用与成员同名的 TOML 数组,样例为 `*_num = 0` + 空数组);数值与改造前的宏/`RadarPara` 默认值一致。
- **内置 toml++ v3.4.0 单头文件(参数配置方式改造)**:新增 `third_party/toml/toml.hpp`(MIT,上游单头文件原样保存);`CMakeLists.txt` 的 `SOURCES` 增加 `data_process_class_dll/rdp_parameter_loader.cpp`、`target_include_directories` 增加 `third_party/toml`;`rdp_parameter_loader.cpp` 用 `#pragma warning(push, 0)` / `(pop)` 包裹第三方头,维持 `/W3` 零告警约定。
- 文档同步(参数配置方式改造):更新 `README.md`(1/2/3/4/5/6/7/8/9/10/11/12/13 节)与 `docs/data_process_flow_report.md` 第 9 节、`docs/track_init_readme.md` 第 13 节等——参数来源改为「TOML 配置文件 → `RDP_parameter`」、宿主调用改为 `parameters_reload`、新增旧参数→新字段对照表、编译期宏清单仅保留非配置常量、已知问题与版本历史同步。
- 新增 `.vscode/scripts/config_paths.bat`:按「构建类型 / 记录模式」推导并校验构建目录 `build/<Debug|Release>/<REC_AUTO|REC_OFF|REC_ON>`(机扫/相扫合并后不再有扫描体制维度),供所有构建脚本共用。
- 新增 `.vscode/scripts/build_config.bat`:对单个配置「配置 + 构建」,失败即中止并返回非 0(取代原 `build_all.bat`)。
- 新增 `.vscode/scripts/clean_all.bat`:删除全部配置的构建输出,并清理旧版 `build/X256_PS`、`build/X256_MS` 目录(`build/output` 打包留档不动)。
- `.vscode/tasks.json` 重建为 5 个任务:「清理全部」「配置并构建(Release/REC_OFF)」「配置并构建(Release/REC_ON)」「配置并构建(Debug/REC_OFF)」「配置并构建(Debug/REC_ON)」;后 4 个任务内部调用 `build_config.bat` 单次配置并构建,默认构建任务为「配置并构建(Release/REC_OFF)」。
- **数据记录改为异步写线程 + 有界队列(数据记录性能优化,17a3d99)**:`rdp_record.h` 新增 `rdp_record::Stats`(`enqueued_rows`/`dropped_rows`/`written_rows`/`transactions`/`write_errors`/`queue_peak_bytes`/`queue_peak_rows`)与 `stats()`,新增 6 个异步入队接口 `WritePoints`/`WriteCohesionTWS`/`WriteCohesionTAS`/`WriteTempTracks`/`WriteTrustTracks`/`WriteAssoInfos`(参数为源结构体 `std::vector` 按值、调用点 `std::move`)及对应宏 `RDP_RECORD_POINTS`/`RDP_RECORD_COHESION_TWS`/`RDP_RECORD_COHESION_TAS`/`RDP_RECORD_TEMP_TRACKS`/`RDP_RECORD_TRUST_TRACKS`/`RDP_RECORD_ASSO_INFOS`;`rdp_record.cpp` 的 `Init()` 建库后启动独立写线程(Windows 下 `THREAD_PRIORITY_BELOW_NORMAL`),处理线程只做「收集源结构体 → 一次入队」,实体构造(数组转文本)与写库全部迁移到写线程。
- **批量事务与 SQLite 配置(数据记录性能优化)**:写线程按表累积待提交数据,达到 `RDP_RECORD_WRITE_BATCH_ROWS`(默认 4096 行)或等待 `RDP_RECORD_WRITE_MAX_WAIT_MS`(默认 50 ms)后以事务批量提交;SQLite 启用 `journal_mode=WAL`(失败回退默认 journal)、`synchronous=OFF`、`cache_size=-8192`(8 MB)、`busy_timeout=2000`;建表后读取 `sqlite_master` 去掉 `AUTOINCREMENT` 重建为普通 `INTEGER PRIMARY KEY`(重建失败时回退并告警)。
- **有界队列与丢弃计数(数据记录性能优化)**:队列上限 `RDP_RECORD_QUEUE_CAP_BYTES`(默认 64 MB)与 `RDP_RECORD_QUEUE_MAX_BATCHES`(默认 4096 批),写线程跟不上时整批丢弃并累计 `dropped_rows`,日志按 `RDP_RECORD_DROP_LOG_INTERVAL`(默认每 1024 行)限频提示;处理线程不因记录阻塞。
- **记录参数宏(数据记录性能优化)**:`parameters.h` 新增可调宏(均以 `#ifndef` 保护,可用编译选项覆盖):`RDP_RECORD_QUEUE_CAP_BYTES`、`RDP_RECORD_QUEUE_MAX_BATCHES`、`RDP_RECORD_WRITE_BATCH_ROWS`、`RDP_RECORD_WRITE_MAX_WAIT_MS`、`RDP_RECORD_ASSO_CHUNK_ROWS`(默认 131072)、`RDP_RECORD_SQLITE_CACHE_KB`、`RDP_RECORD_SQLITE_BUSY_TIMEOUT_MS`、`RDP_RECORD_SQLITE_WAL`、`RDP_RECORD_MAX_CONSECUTIVE_ERRORS`(默认 10)、`RDP_RECORD_DROP_LOG_INTERVAL`。
- **记录健壮性与重连(数据记录性能优化)**:写失败每表只提示一次并永久跳过该表,连续错误达到 `RDP_RECORD_MAX_CONSECUTIVE_ERRORS` 次全局熔断并丢弃队列;`Shutdown()` 改为「停止入队 → 排空队列与 pending → 打印 stats → `wal_checkpoint(TRUNCATE)` → 关库」;支持 `Destroy()` → `GetB()` 重连重新建库(同一秒文件名冲突时追加 `_1`/`_2` 后缀)。
- **航迹质量信息入库(1ffb287)**:`RdpRecTempTrack` 新增 `quality`(临时航迹质量);`RdpRecTrustTrack` 新增 `Quality`(可靠航迹质量)、`Unupdated_time`(未更新累计毫秒)、`Point_sum`(累计点数)、`Valid_point_sum`(累计实点关联次数),`rdp_record.cpp` 注册顺序同步——质量纳入调试可查范围,可直接用 SQL 分析「起批质量 / 关联后质量变化 / 未关联衰减」。
- 文档:全面更新 `docs/data_record_debug.md`(异步运行期行为、接口与宏一览、新增记录点四步配方、6 张表记录点清单、已知限制、验证参考)与 `README.md` 相关章节(2、3、4.3、5.2、7.1、10.1、11.2、11.3、12、13 节)。
### Changed
- **日志组件行为改为 spdlog(日志组件 spdlog 改造,24ce1e6)**:`data_process_class_dll.cpp` 的 `Data_Process_Factory::GetB()` 每次创建实例时调用 `rdp_log::Init()`(在 `rdp_record::Init()` 之前,使记录模块初始化日志也写入同一文件),`Destroy()` 在 `rdp_record::Shutdown()` 之后调用 `rdp_log::Shutdown()`(flush 并关闭日志文件);不再使用 `rdbuf()` 重定向/恢复 `std::cout`,`std::cout` 保持宿主原始语义,日志的双写完全由 spdlog 双 sink 完成;`RDP_LOG_DIR` 路径与 `.\rdp_logs\` 命名风格保持与旧实现一致。全部 `RDP_LOG` 调用点按语义迁移为级别宏:`data_process.cpp`(`parameters_reload` 成功 INFO / 失败 ERROR)、`track_asso_tas.cpp`(ΔT≤0 告警 WARN)、`rdp_record.cpp`(19 处:初始化/统计/建表/关闭 INFO,PRAGMA 回退/队满丢弃/时间获取失败/数据库打开失败 WARN,写入失败/连续错误熔断/写线程异常 ERROR),其余注释代码中的旧宏引用同步改为对应级别宏注释;`parameters.h` 的日志说明改为 spdlog + `SPDLOG_ACTIVE_LEVEL`/`RDP_LOG_LEVEL` 描述。
- **日志改造的第三方告警隔离**:spdlog 静态库第三方源码以目标级 `/w` 编译,包含其头文件的 `rdp_log.h` 用 `#pragma warning(push, 0)` / `(pop)` 包裹(README 4.5 已同步为第三个第三方告警例外),项目源码仍保持 `/W3` 零告警。
- **本次日志改造的验证(Lead 实测,2026-10-28)**:① 构建:4 个配置(Release/Debug × REC_OFF/REC_ON)经 `.vscode/scripts/build_config.bat` 全部成功、退出码 0、**0 warning / 0 error**;重复执行无编译步骤(增量幂等);`RDP_LOG_LEVEL=BAD` 在 configure 阶段被拒绝(退出码 1)。② compile_commands:Debug 配置 `-DSPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE`、Release 配置 `-DSPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_INFO`(AUTO),各目标均含 `-DSPDLOG_COMPILED_LIB`,REC_ON 的 `rdp_record.cpp`(C++20)与 DLL 源文件定义一致。③ DLL 体积:Release REC_OFF 537,088 B(较改造前 331,264 B +205,824 B)、Release REC_ON 1,855,488 B(+202,752 B)、Debug REC_OFF 3,317,760 B(+1,602,560 B)、Debug REC_ON 5,867,520 B(+1,175,552 B),增量来自内置 spdlog/{fmt}(DLL 公开接口与业务数值行为不变);导出表仍 **31 项**(无 spdlog 符号导出),导入表仅 KERNEL32/MSVCP140/VCRUNTIME140*/api-ms-win-crt-*,无新增第三方 DLL 依赖(spdlog 为静态库)。④ 运行期(4 配置逐一编译并运行 `build/verify/build_api_test.bat`,各 38 项断言 0 失败):同一批日志消息在 stdout 与日志文件中逐行一致;日志文件名匹配 `rdp_\d{8}_\d{6}(_\d+)?\.log`,每次 GetB 一个文件、同秒 Destroy→GetB 重连产生 `_1` 后缀新文件;日志行匹配 `[YYYY-MM-DD HH:MM:SS.mmm] [级别] [文件:行号] 内容`(中文消息正常),REC_ON 配置可见 `[rdp_record]` 的 `journal_mode`/`enabled`/`table ... created without AUTOINCREMENT`/`stats`/`shutdown` 等日志并随双写落盘。⑤ 编译期级别隔离实测:在 `build/verify/` 下独立配置构建 `RDP_LOG_LEVEL=WARN` 与 `=OFF`(不污染标准构建目录)均 0 warning;WARN 构建运行期 stdout/日志无 `[info]` 行、仅有 `[error]`(失败路径)且日志文件仍创建;OFF 构建运行期不创建 `rdp_logs` 目录/文件、stdout 无任何日志行、接口退出码正常。⑥ 静态检查:源码已无 `RDP_LOG <<` 与 `RDP_LOG_ENABLE` 残留;`rdp_log.h` 提供 6 个级别宏 + `RDP_LOG_ERR` 别名;`GetB()/Destroy()` 已实际调用 `rdp_log::Init()/Shutdown()`(非注释)。验证程序/日志位于 git 忽略的 `build/verify/`,未入库;另由独立验证者以冻结源码快照复验全部验收项(4 配置干净构建 0 warning/0 error、双写/文件名/行格式/逐行一致、WARN/OFF 隔离、dumpbin 导出与依赖、幂等与非法值),结论一致,报告见 `build/verify/verification_report.md`(未入库)。
- **工作参数改为全局对象(全局工作参数对象改造,7e4cb29)**:`struct.h` 在 `struct RDP_parameter` 之后新增 `extern RDP_parameter rdp_parameter;`(附注释:所有处理模块直接访问、定义在 `data_process.cpp`、由 `parameters_reload()` 在配置完整校验通过后整体赋值、处理期间只读、配置重载应与处理互斥);`data_process.cpp` 定义并零初始化 `RDP_parameter rdp_parameter{};`(当前 L14);`Data_Process` 删除 `RDP_parameter rdp_parameter` 成员(`data_process.h`,保留注释说明工作参数为全局对象);`parameters_reload()` 改为直接调用 `rdp_parameter_load(parameter_file_path, rdp_parameter, &error)`——loader 仅在全部校验通过后写入目标对象,失败不写入,故「失败保持当前参数」语义不变。删除各处理模块(`kalman` 11 个方法、`dot_coh`/`dot_coh_tas`、`track_asso`/`track_asso_tas`/`track_asso_direct_tracking`、`track_init`/`track_init_direct_tracking`、`track_die`/`track_die_tas`、`track_index_mangement`、`tas_ctrl`,共 12 个模块的 24 个 .h/.cpp 文件)全部 `const RDP_parameter& rdp_parameter` 形参及调用实参,函数体内 `rdp_parameter.<field>` 表达式一处未改、现直接解析到全局对象;公开接口签名、公式、阈值与数值行为均不变。**注意**:全局对象是 DLL 内单实例可变状态,`parameters_reload()` 与处理调用之间非线程安全(与 BUG-03 的线程模型约定一致,宿主应按单线程接口使用、重载配置时不得并发处理)。
- **本次全局工作参数对象改造的验证(Lead 实测,2026-10-27)**:① 构建:4 个配置(Release/Debug × REC_OFF/REC_ON;机扫/相扫已合并为单一构建)全部成功、退出码 0、**0 warning / 0 error**。② DLL 体积:Release REC_OFF 331,264 B(较改造前 331,776 B 略减 512 B)、Release REC_ON 1,652,736 B、Debug REC_OFF 1,715,200 B、Debug REC_ON 4,691,968 B。③ 导出表(Release REC_OFF,共 31 项):`parameters_reload` 仍在列(`?parameters_reload@Data_process_class_dll@@UEAAHPEBD@Z`);全局对象 `rdp_parameter` 未导出(DLL 内部符号),宿主接口不变。④ 加载器单元测试 233 项断言全部通过(加载器接口未变)。⑤ 公开接口运行期测试:4 配置逐一编译链接运行,各 38 项断言全部通过(TWS/TAS 主流程冒烟、`parameters_reload` 成功/失败、`scan_mode` 判圈运行时切换、`Destroy()`→`GetB()` 重连等)。⑥ 静态检查:处理模块已无 `const RDP_parameter&` 形参(仅 `rdp_parameter_loader.cpp` 的 `validate_parameter(const RDP_parameter&, ...)` 校验辅助函数保留);全局对象声明在 `struct.h:285`、定义在 `data_process.cpp:14`;所有模块内 `rdp_parameter` 仅以 `.field` 表达式出现;`data_process.cpp` 仅有的传参是 `parameters_reload()` 中的 `rdp_parameter_load(..., rdp_parameter, ...)`。⑦ 记录点当前行号:TWS `RDP_RECORD_POINTS` 在 `data_process.cpp:75`、TAS 分支在 `data_process.cpp:153`。验证程序与日志位于 git 忽略的构建目录中,未入库。
- **TWS 判圈改由配置项 `scan_mode` 运行时选择(参数配置方式改造)**:`data_process.cpp::data_preprocess()` 原用 `#ifdef PHASE_SCANNING / #else / #endif` 在编译期隔离两套判圈代码,现改为按 `rdp_parameter.scan_mode` 运行时选择——`scan_mode==0`(相扫)判 `Beam_index_aiz <= last_beam_num`,`scan_mode==1`(机扫)判 `Beam_index_aiz == 0`;两种情况都会更新 `last_beam_num`、仅判满圈时 `circle_id++` 并返回 1。行为与旧两套编译期分支**逐分支等价**(相扫首帧因 `last_beam_num` 初值 INT_MAX 仍判满圈;机扫仍以波位号 0 判满圈)。`PHASE_SCANNING` / `MECHANICAL_SCANNING` 宏与 `RADAR_SCAN_MODE` 缓存变量随后在「机扫/相扫版本合并」中一并移除(见 Removed),构建不再区分体制;判圈只由配置项 `scan_mode` 决定。
- **本次 `scan_mode` 改动的验证(Lead 实测,2026-10-27,机扫/相扫合并前)**:① 构建:8 个配置(X256_PS/X256_MS × Release/Debug × REC_OFF/REC_ON)全部重建成功、退出码 0、0 warning 0 error;Release REC_OFF DLL 仍为 331,776 字节、Debug REC_ON 4,694,016 字节(与 `scan_mode` 改动前一致)。② 加载器单元测试 233 项断言全部通过:新增覆盖「样例 `scan_mode=0` 取值正确」「`scan_mode=2` 时加载返回 -1 且错误信息包含 `scan_mode`」,其余既有成功/失败路径(缺文件、语法错、缺键、类型错、未知键、缺段、越界、数组长度不符等)保持通过。③ 公开接口运行期测试(8 配置逐一编译链接运行)各 38 项断言全部通过(原 29 项 + 新增 9 项判圈运行时切换断言):同一份二进制加载样例(`scan_mode=0` 相扫)后波位号 24 → `data_preprocess` 返回 1(判满圈);加载临时配置(`scan_mode=1` 机扫)后波位号 24 → 返回 2、波位号 0 → 返回 1;再切回样例配置并清空后行为一致;PS 与 MS 构建的测试结果相同,证明判圈已完全由配置决定。④ 静态检查:`data_process.cpp` 中 `PHASE_SCANNING`/`MECHANICAL_SCANNING` 已无有效代码引用(仅注释性提及);宏仍存在于 `parameters.h`(`VERSION_INFO` / `#pragma message`)与 `CMakeLists.txt`(`RADAR_SCAN_MODE` 编译定义、构建目录/任务命名);`RDP_parameter` 成员数 = 加载器键数 = TOML 键数 = **63**(新增 `scan_mode` 后),11 个配置段不变。⑤ 该轮记录点行号:TWS `RDP_RECORD_POINTS` 在 `data_process.cpp:72`、TAS 分支在 `data_process.cpp:150`(全局工作参数对象改造后变为 `:75` / `:153`,见本区上方新增验证)。验证程序与日志位于 git 忽略的构建目录中,未入库。
- **参数来源改为「TOML 配置文件 → `RDP_parameter`」(参数配置方式改造,423c200)**:原先「宿主下发 `struct RadarPara` + `parameters.h` 编译期宏」两条参数来源统一为运行期配置文件;`struct.h::RDP_parameter` 成为工作参数唯一来源,初始由 `Data_Process::rdp_parameter{}`(零初始化)持有,`parameters_reload()` 成功后才整体替换(**后于「全局工作参数对象改造」改为 `struct.h::rdp_parameter` 全局对象,见本区上方**)。改造只替换参数来源,不改变公式、阈值语义与处理流程,默认数值与改造前一致。
- **各模块参数传递方式(参数配置方式改造)**:`kalman`(使用量测误差/频点的 11 个方法:`kalman_filter_init_2dots`、`d_cal_track_init`、`kalman_filter_init_3dots`、`d_cal`、`d_cal_EKF`、`d_cal_with_doppler`、`kalman_filter_EKF`、`kalman_filter`、`d_cal_track_init_EKF`、`d_cal_track_init_with_doppler`、`Bind_speed`)、`dot_coh`/`dot_coh_tas`、`track_asso`/`track_asso_tas`/`track_asso_direct_tracking`、`track_init`/`track_init_direct_tracking`、`track_die`/`track_die_tas`、`track_index_mangement`、`tas_ctrl` 的入口与内部函数新增 `const RDP_parameter& rdp_parameter` 形参(原按值传递的 `struct RadarPara Work_Parameter` 全部删除);无参数依赖的 `kalman_pred` 保持原签名(**后于「全局工作参数对象改造」删除这些形参、改为直接访问全局对象,见本区上方**)。
- **旧宏与旧 `RadarPara` 字段的等价替换(参数配置方式改造)**:如 `SIGMA_R/A/E/V`→`sigma_range/azimuth/pitch/velocity`、`DOT_COH_RANGE/V/AZI`→`point_cohesion_range/velocity/azimuth`、`R_MIN/R_MAX`→`point_cohesion_range_min/max`、`FREQ0`→`freq_0_GHz`、`ASSO_THORD`→`associate_threshold`、`MAX_EXTRAPOLATE_NUM(_TAS)`→`extrapolate_max(_tas)`、`H_F_WIN_LEN`→`height_smooth_win`、`TRACK_START_THRESHOLD/ALPHA_START`→`track_init_threshold/track_init_angle`、`TRACK_START_QUALITY_L3~L7`→`track_init_quality_L3~L7`、`TRACK_DIE_QUALITY(_PRAMARY)/TRACK_DIE_ROUND(_PRIMARY/_TAS)`→`track_die_quality(_pramary)/track_die_round(_pramary/_tas)`、`TRACK_QUALITY_*`→`track_quality_*`、`work_mode/Height/Sys_delay`→`radar_work_mode/radar_height/radar_system_delay`、`track_start_point_num`→`track_init_point_num_min`、`V_MAX/V_MIN`→`track_init_velocity_max/min`、`track_prohibite_area_*`→`track_init_prohibite_area_*`、`TAS_prohibite_area_*`→`tas_prohibite_area_*`、`Model1/2/3_Q_fast/slow`→`model_1/2/3_Q_fast/slow`、`DATA_RATE_SHORT/MIDDLE/FAR`→`data_rate_tws`(契约确认的等价改法)、`DATA_RATE_TAS`→`data_rate_tas`;完整对照见 `README.md` 10.3 节。
- **航迹号、TAS 队列与跟踪波束类型改为按配置分配(参数配置方式改造)**:`track_index_mangement.cpp` 的定长数组 `int List[MAX_TRACK_INDEX]` 改为按 `rdp_parameter.track_index_max` 分配的 `std::vector<int>`;TAS 队列固定容量改用编译期常量 `RDP_TAS_QUEUE_CAPACITY`(30),运行期同时跟踪目标数上限改用配置项 `tas_track_num_max`(加载器校验 0~30,原 `MAX_TAS_NUM` 相扫 4 / 机扫 1);跟踪波束 `Tracking_beam->type` 由硬编码 1 改为配置项 `default_track_beam_type`。
- **`parameters.h` 精简为编译期常量(参数配置方式改造)**:删除全部雷达工作参数宏,仅保留 `PI`、版本宏(`MAJOR_VERSION`/`MINOR_VERSION`/`PATCH_VERSION`/`VERSION_INFO`/`VERSION_STRING`)、日志宏(`RDP_LOG_*`)、记录宏(`RDP_RECORD_*`)、接口容量常量 `MAX_TRACK_NUM`(500)/`RDP_TAS_QUEUE_CAPACITY`(30)与 `Round`;`requirements.md` 同时改写为本次参数配置方式改造的需求说明。
- **本次改造的验证(Lead 实测,2026-10-27,机扫/相扫合并前)**:① 构建:8 个配置(X256_PS/X256_MS × Release/Debug × REC_OFF/REC_ON)全部成功、退出码 0、0 warning 0 error,每个配置均生成 `bin/data_process_class_dll.dll`;Release REC_OFF DLL 体积 331,776 字节(约 324 KB,较改造前约 178 KB 增大,原因是纯头文件的 toml++ 解析器编入 DLL),Debug REC_ON 为 4,694,016 字节。② 导出表(Release REC_OFF,共 31 个导出):新增 `parameters_reload`(`?parameters_reload@Data_process_class_dll@@UEAAHPEBD@Z`),`track_process_parameters_initial`/`track_process_parameters_modify` 与 `RadarPara` 相关符号已不存在,`Data_Process_Factory::GetB/Destroy` 等其余接口不变。③ 加载器单元测试(229 项断言全部通过):样例配置 62 个键逐字段取值正确(含整数/浮点与负的二次/一次衰减系数;该轮为 `scan_mode` 新增前的验证,新增后样例共 63 个键,见上一条 `scan_mode` 验证);文件不存在、空路径、nullptr、TOML 语法错误、缺标量键、类型错误、未知键、缺配置段、取值越界(起批点数 2、`data_rate_tas=0`)、区域数 31、区域方位 min≥max、数组长度与 `*_num` 不一致等失败路径均返回 -1 且不修改当前参数,失败后可重新加载成功;屏蔽区数组按 `*_num` 读取、未使用位置保持 0。④ 公开接口运行期测试(8 个配置逐一编译链接运行,各 29 项断言全部通过):`GetB()`/`version_info`(`V1.5.8.PS`/`V1.5.8.MS`)、`parameters_reload` 成功/失败返回值与「失败后再加载成功」、TWS/TAS 主流程冒烟、`Beam_Ctrl`、`track_clear_all`、`track_delete`/`tracking_start`/`tracking_stop`/`primary_target_add`/`primary_target_cancel`/`tracking_point`、`Destroy()` 后重新 `GetB()` 并再次加载配置均正常。⑤ 静态审计:源码无 `Work_Parameter`/`RadarPara`/旧参数宏残留(区分大小写整词审计 0 命中);首轮验证时 62 个 `RDP_parameter` 成员 ↔ 62 个加载器键 ↔ 62 个 TOML 键一一对应(新增 `scan_mode` 后为 63 个),11 个配置段与加载器一致。验证程序与日志位于 git 忽略的构建目录中,未入库。
- **构建脚本适配机扫/相扫合并(本次构建配置调整)**:构建目录由「扫描体制 × 构建类型 × 记录模式」改为「构建类型 × 记录模式」——`build/<Debug|Release>/<REC_AUTO|REC_OFF|REC_ON>`,不再有 `X256_PS`/`X256_MS` 维度。
- `config_paths.bat` / `configure.bat` / `build.bat` / `clean.bat` 参数改为 `<Debug|Release> [AUTO|ON|OFF]`,构建目录自动推导;`build_all.bat` 由 `build_config.bat`(单配置「配置 + 构建」)取代,失败即中止并返回非 0;`clean_all.bat` 在清理 4 种配置的同时清理旧版 `build/X256_PS`、`build/X256_MS` 目录(`build/output` 保留)。
- VS Code 配置同步:`settings.json` 的 `cmake.buildDirectory` 改为 `${workspaceFolder}/build/${buildType}/${variant:recordMode}`、`compileCommands` 指向 `build/Debug/REC_ON/compile_commands.json`;`cmake-variants.yaml` 移除 `scanMode` 变体(仅保留 `buildType` + `recordMode`);`c_cpp_properties.json` 合并为单个「X256 (MSVC2022 x64)」配置(不再带 `PHASE_SCANNING`/`MECHANICAL_SCANNING` 宏);`launch.json` 的 `preLaunchTask` 改为「配置并构建(Debug/REC_ON)」,`program`/`symbolSearchPath` 指向 `build/Debug/REC_ON/bin`。
- 更新 README(3 节目录结构、4.2~4.5 节构建说明、7.1/10.4/11.2/12/13 节)与 `docs/data_record_debug.md` 第 2 节、`docs/data_process_flow_report.md` 判圈/参数说明、`docs/data_record_perf_report.md` 中的构建路径;`requirements.md` 仍为参数配置方式改造需求(本次未改需求文档)。
- **本次构建配置调整的验证(Lead 实测,2026-10-27)**:`CMakeLists.txt` 移除 `RADAR_SCAN_MODE` 后 4 个配置(Release/Debug × REC_OFF/REC_ON)重建成功、退出码 0、0 warning 0 error(已把 `rdp_record` 的 `/W3`、`/Zc:__cplusplus` 限定为 C++ 源文件,消除 sqlite3.c 上 `/w` 覆盖 `/W3` 的 D9025 命令行提示;C 文件仅保留 `/utf-8`/`/FS` 与源文件级 `/w`);产物 DLL 大小 Release REC_OFF 331,776 / Release REC_ON 1,653,248 / Debug REC_OFF 1,718,272 / Debug REC_ON 4,694,528 字节;`compile_commands.json` 中不再含 `PHASE_SCANNING`/`MECHANICAL_SCANNING`/`RADAR_SCAN_MODE`,REC_ON 配置含 `RDP_RECORD_ENABLE`;5 个任务实际执行返回 0;「清理全部」首次执行删除旧版 `build/X256_PS`、`build/X256_MS`,再次执行删除重建后的 4 个新配置目录(`build/output` 保留);已构建配置重复执行 `build_config.bat` 为 0 个编译/链接步骤(增量幂等);模拟 MSVC 环境无效时 `build_config.bat` 立即中止(退出码 1);非法构建类型/记录模式与未配置即构建均返回非 0。
- **12 个业务记录点由「同步构造实体写库」改为「帧/圈内收集源结构体 → 一次入队」(数据记录性能优化)**:`data_process.cpp`(TWS/TAS 输入点迹)、`dot_coh.cpp`、`dot_coh_tas.cpp`、`track_init.cpp`(临时航迹)、`track_asso.cpp`(可靠航迹 + 关联信息按 `RDP_RECORD_ASSO_CHUNK_ROWS` 分块)、`track_asso_tas.cpp`(同)共 12 处;记录时机、条件与记录行数/表列均保持不变;6 个业务文件删除 `#include "rdp_record_entity.h"`,所有新增代码仍在 `#ifdef RDP_RECORD_ENABLE` 内,记录关闭构建零代码、零影响。
- **记录线程模型与 Shutdown 语义(数据记录性能优化)**:由「处理线程单连接 + 互斥锁串行同步写」改为「处理线程只入队 + 独立写线程承担实体构造与全部写库」;`RDP_RECORD`/`RDP_RECORD_BATCH` 保留为遗留同步接口(在调用线程直接写库、会阻塞,仅供测试/特殊场景);`Shutdown()` 会**排空**队列与 pending 后再关库,并在关库前打印统计。
- **未改变记录语义(数据记录性能优化)**:不采用 P0-a(关联信息裁剪)与 P2(记录策略/轮转),⑥ 仍按每个候选一行写入;跨表写入顺序仍不保证、各表内顺序保持;`REC_OFF` 构建零影响与优化前一致。
- **本次优化的验证(可复查,机扫/相扫合并前)**:8 种配置(X256_PS/X256_MS × Release/Debug × REC_OFF/REC_ON)0 warning 0 error(合并后 4 种配置的构建验证见上文「构建脚本适配机扫/相扫合并」);dup 场景 6 圈/200 目标/每帧 150 点 REC_ON vs REC_OFF:普通帧每帧新增 p99 ≈ 0.09~0.11 ms(≤1 ms 阈值内)、判圈帧 `track` 新增中位 ≈ 2.9~4.1 ms(压测 p99 ≈ 13~24 ms,因未采用 P0-a,属压测上限),受控 50 目标/50 点场景判圈帧新增中位约 3 ms、最大 5.96 ms;记录行数与改动前完全一致(`RdpRecPoint` 90,000 / `RdpRecPointCohesionTWS` 4,170 / `RdpRecAssoInfo` 211,584 / `RdpRecTempTrack` 8,330 / `RdpRecTrustTrack` 304,合计 314,388),`integrity_check=ok`、DDL 无 `AUTOINCREMENT`、`journal_mode=wal`、id 严格递增、`dropped_rows=0`;60 圈长跑 9,987,370 行、`dropped_rows=0`、`write_errors=0`、队列峰值约 19.8 MB、DB 约 1.06 GB;故障注入(记录目录不可建时 `Init` 静默失败、驱动正常;小队列测试构建丢弃 314,388 行且计数正确)与 `Destroy()`→`GetB()` 重连(两个独立 db 均正常写入)测试通过。原始日志与验收脚本位于 git 忽略的 `build/analysis/verify/`。
- **相扫 TAS 航迹消亡圈数放宽(5d105d2,`parameters.h`)**:`TRACK_DIE_ROUND_TAS` 由 10 调整为 **20**(仅相扫;机扫保持 96)。因判据为 `Extrapolate_round > 阈值`,相扫 TAS 航迹实际由「连续 11 圈无实点关联消亡」放宽为「连续 21 圈」才消亡,便于 TAS 目标在短时丢点后重新关联。
- **TAS 高度平滑窗口加长(6284f3f)**:`track_asso_tas.cpp::track_hight_update()` 的分段偏移由 `H_F_WIN_LEN + {2,3,4,6}` 改为 `H_F_WIN_LEN + {6,8,10,12}`;同时相扫 `H_F_WIN_LEN` 由 3 调整为 **6**(机扫仍为 3)。合并效果:相扫 TAS 窗口 12/14/16/18 点、机扫 TAS 窗口 9/11/13/15 点(原为 5/6/7/9);TWS 仍用 `H_F_WIN_LEN + {2,3,4,6}`,相扫因此由 5/6/7/9 变为 **8/9/10/12**、机扫保持 5/6/7/9。
### Fixed
- **修复手动转 TAS 时输出航迹更新信息不完整(eb9f30c,`tas_ctrl.cpp::tas_target_add()`)**:目标由 TWS 手动转 TAS 的当圈,原先只填 `Point_Sum`/`Range`/`Azimuth`/`Track_Index`/`Range_V`/`Amplitude`/`Flag_Point`/`Track_Mode`/`Elevation`/`z`/`Target_Type`/`track_snr` 等少数字段,其余字段(`track_time`/`GNSS_time`/`Quality`/`Direction_Angle`/关联点信息/`Flag_Direction`/`track_rcs`/`pitch_num`/`x,y,v_x,v_y`/十字星数据等)保持宿主旧值;现改为与常规关联输出**同一套字段填充**(含按 `radar_system_delay` 补偿后的极坐标、`Quality × track_quality_max`、`Flag_Direction` 的航向-位置夹角判据、十字星 `memcpy`),并补写 `point_type=0`,避免宿主在转跟踪当圈读到过期字段。
- 文档同步「航迹指纹」(本次文档更新):README(2/6.1/6.2/7.1/8.2/9.10/新增 9.12/11.2/12 节)、`docs/data_process_flow_report.md`(2.2/7.1/7.2/8.1/8.2 与第 10 节)、`docs/track_init_readme.md`(起批时计算指纹)、`docs/data_record_debug.md`(记录实体未含指纹的说明)同步指纹的生成、下发与匹配语义,并明确 `DataRev::TAS_track_index` / `TrackingBeam::TAS_track_index` 字段名未变、语义已改为指纹。
- 文档同步「日志组件 spdlog 改造」(本次文档更新):`README.md`(1 技术栈/2 功能特性/3 目录结构/4.1 环境/4.3 构建选项/4.4 产物体积/4.5 第三方告警例外/5.2 模块职责/7.1 日志说明/9.6 历史注/10.1 失败日志/10.3 旧宏说明/10.4 宏表/12 版本历史/13 参考文档)、`docs/data_process_flow_report.md`、`docs/data_record_debug.md` 及相关 docs 中关于 `RDP_LOG`/`RDP_LOG_ENABLE`/`rdbuf()` 重定向的过期表述统一改为 spdlog 双写 + 级别宏 + `SPDLOG_ACTIVE_LEVEL`/`RDP_LOG_LEVEL` 编译期控制;`requirements.md` 说明同步为本次日志改造需求(其文件本身保持需求原文未改)。
- 文档同步「全局工作参数对象改造」改动(本次文档更新):README 3/4.4/5.1/5.2/6.2/7.1/10.1/11.2/12 节、`docs/data_process_flow_report.md` 第 9 节、`docs/track_init_readme.md` 1.1/2.3/8.1~8.3/16.2 节、`docs/data_record_debug.md` 记录点行号——工作参数表述由「`Data_Process::rdp_parameter` 成员 / 函数形参逐层传入」改为「`struct.h::rdp_parameter` 全局对象(`data_process.cpp` 定义)」,同步删除文中传递实参的代码片段与表格行,记录点行号更新为 `data_process.cpp:75`(TWS)/ `:153`(TAS)。
- 文档同步「`scan_mode` 运行时判圈」改动(本次文档更新):README 1/2/3/4.2/7.2/8.1/10.1/10.2/10.3/10.4/11.2/12/13 节、`docs/data_process_flow_report.md` 2.2 与第 9 节、`docs/track_init_readme.md` 1.2 与输出字段表、`docs/data_record_debug.md` 记录点行号(`data_process.cpp:72` TWS / `:150` TAS)——判圈表述由「编译期 `PHASE_SCANNING`/`MECHANICAL_SCANNING` 隔离」改为「由配置项 `scan_mode` 运行时选择」,并同步 `UNCONF_TARGET` 宏删除后的语义写法。
- 修正文档中把参数来源写成「`parameters.h` 编译期宏 / 宿主下发 `RadarPara`」的过期描述(本次文档更新,对应参数配置方式改造):README 2、3、4.2、5.1、5.2、6.1、6.2、7.1、7.3、8.1~8.3、9.1~9.11、10、11、12、13 节与 `docs/data_process_flow_report.md` 第 9/10 节、`docs/track_init_readme.md` 第 1/2/4/6/8/10/11~16 节按源码同步——参数来源改为 TOML 配置文件 → `RDP_parameter`、旧宏改记为「原 …」并给出新字段名、新增 `parameters_reload` 调用方式与旧→新对照,数值未变。
- 修正文档中 TAS 航迹消亡圈数与高度平滑窗口的过期数值(本次文档更新):README 2/5.2/9.7/9.9/10.1 节与 `docs/data_process_flow_report.md` 4.6、6.1、9.1 节按 5d105d2、6284f3f 同步为「相扫 TAS 20 圈 / 机扫 96」、「相扫 H_F_WIN_LEN=6、TAS 偏移 +6/+8/+10/+12」。
- 修正 `docs/data_record_debug.md` 中「质量字段尚未加入记录实体」的过期描述(本次文档更新,对应 1ffb287):字段速查与记录点说明补齐 `quality` / `Quality` / `Unupdated_time` / `Point_sum` / `Valid_point_sum`。
- 文档同步「机扫/相扫版本合并」后的构建配置(本次文档更新):README 3/4.2~4.5/7.1/10.4/11.2/12/13 节、`docs/data_record_debug.md` 第 2 节、`docs/data_process_flow_report.md` 2.2 与第 9 节、`docs/data_record_perf_report.md` 构建路径——构建目录去掉扫描体制维度、脚本签名与任务名同步、版本串改为 `V1.5.9.GENERIC`、删除「构建体制宏」相关说明。
### Removed
- **删除旧日志开关与旧日志宏接口(日志组件 spdlog 改造)**:`parameters.h` 删除 `RDP_LOG_ENABLE` 宏(改由 `SPDLOG_ACTIVE_LEVEL` 编译期控制级别);`rdp_log` 不再提供 `RDP_LOG` 流式宏与 `rdp_log::prefix()`(改为 `RDP_LOG_*` 级别宏,时间戳/级别/文件:行号由 spdlog pattern 统一输出);删除 `rdp_log::Init()` 中 `std::cout.rdbuf()` 重定向及 `rdp_log::Shutdown()` 中的恢复逻辑(内部状态改为持有 spdlog logger 与日志路径)。
- **删除 `struct RadarPara` 与两个旧公开虚接口(参数配置方式改造)**:`data_process_class_dll.h` 中的 `struct RadarPara` 及 `track_process_parameters_initial(RadarPara)` / `track_process_parameters_modify(RadarPara)` 整体删除,由 `parameters_reload(const char*)` 取代;`data_process.h` 中的 `struct RadarPara Work_Parameter` 删除。旧 `RadarPara` 中实现未使用的字段(`R_TAS_max/min`、`V_TAS_max/min`、`TAS_Height_1~5`、`cfar_th`、`north_angle`、`Beam_num`)未进入新结构体(相应自动 TAS 逻辑仍保持停用)。
- **删除 `parameters.h` 中的雷达工作参数宏(参数配置方式改造)**:`SIGMA_R/A/E/V`、`DOT_COH_RANGE/V/AZI`、`R_MIN`/`R_MAX`、`FREQ0`、`ASSO_THORD`、`MAX_EXTRAPOLATE_NUM(_TAS)`、`H_F_WIN_LEN`、`TRACK_START_THRESHOLD`、`ALPHA_START`、`TRACK_START_QUALITY_L3~L7`、`TRACK_DIE_*`、`TRACK_QUALITY_*`、`MAX_TRACK_INDEX`、`MAX_TAS_NUM`、`TAS_QUEUE_LENGTH`、`DATA_RATE_*`(含 `H_F_WIN_LEN`、`MAX_BEAM_NUM`、`ASSOCIATE_THRESHOLD_MIN/MID/MAX`、`DIRECT_TRACKING_QUEUE_LENGTH` 等)以及已注释的 `V_MAX`/`V_MIN` 残留;这些参数改由配置文件提供(见 `README.md` 10.1~10.3)。
- **删除 `UNCONF_TARGET` 宏(参数配置方式改造)**:`parameters.h` 的未确认目标类型语义常量 `UNCONF_TARGET`(0)删除,`track_init.cpp` / `track_init_direct_tracking.cpp` 起批处 `Target_Type` 改为直接写 0,语义不变。
- **移除构建期的机扫/相扫版本区分(机扫/相扫版本合并)**:`CMakeLists.txt` 删除 `RADAR_SCAN_MODE` 缓存变量、取值校验与 `target_compile_definitions` 中的 `${RADAR_SCAN_MODE}`;构建不再定义 `PHASE_SCANNING` / `MECHANICAL_SCANNING`,`parameters.h` 的 `VERSION_INFO` 由 PS/MS 改为固定 `"GENERIC"`(版本号 1.5.9,版本串 `V1.5.9.GENERIC`);扫描模式只由运行期配置项 `[radar_params] scan_mode` 决定。构建目录/脚本/任务随之不再有扫描体制参数与 `X256_PS`/`X256_MS` 维度。
- 删除 `tasks.json` 中旧的 `CMake: 配置/构建/清理/配置并构建...` 任务(15 个),改为上述 5 个任务(机扫/相扫合并后为「清理全部」+ 4 个「配置并构建(...)」)。
## [1.5.8] - 2026-10-20
> 本次发布把 [1.5.6] 之后、文档未及同步的全部改动一并归档(提交 5cb67cc … 3088ce4)。
> 说明:中间代码状态 b4aed8a 曾把 `PATCH_VERSION` 提升为 7,但该版本未单独发布/记录,故本区块按 1.5.8 汇总;
> 版本号宏位于 `parameters.h`,本次最终为 **1.5.8**(3088ce4「更新版本号至V1.5.8」)。
### Added
- **航迹质量(Track Quality)(1ae0cf2 / d9a7bd0 / 4b70b24 / 782bff5)**:新增归一化的航迹质量体系(0~1),并在三处维护——① **临时航迹阶段**(`track_init.cpp::point_temp_track_asso()`)每延长一个点即重算质量:`d_tmp = exp(−d)`(`d` 为末点统计距离)、`snr_tmp = 1/(1+exp(−(SNR−16)/1.2))`,按 `TRACK_QUALITY_D_WEIGHT`(0.5) 与 `TRACK_QUALITY_SNR_WEIGHT`(0.3) 归一化加权;长度恰为 3 时对三个点取平均 SNR 并写满 3 点,长度 > 3 时只对末点做指数平滑(`前值×0.8 + 新值×0.2`);② **关联更新**(`track_asso.cpp::model_filter()`)在 IMM/EKF 更新后按 `d`(`exp(−d_min)`)、点迹 SNR 与「有效点数/有效点占比」三项加权(权重 0.5/0.3/0.2)得即时质量,再与历史质量指数平滑;③ **外推递减**(见 Changed)。质量在起批时随临时航迹末点带入可靠航迹,并作为普通 TWS 航迹的消亡依据。
- **航迹质量对外输出(1ae0cf2)**:`Track` 结构体在 `Amplitude` 之后新增 `float Quality`(航迹质量),输出时按 `Quality × TRACK_QUALITY_MAX`(×100)折算为 **0~100**;TWS 关联/外推输出(`track_asso.cpp`)与起批历史点输出(`track_init.cpp`)均已写入。⚠ 该字段插在结构体中部,**宿主必须用同一版本头文件重新编译**。
- **起批质量门限(d9a7bd0)**:`track_init.cpp::tmp_track_to_trust_track()` 的起批条件在「长度 3~10 点 + 不在起批屏蔽区」之外新增**按长度分级的最低质量**——3 点 ≥0.7、4 点 ≥0.6、5 点 ≥0.5、6 点 ≥0.4、7 点及以上 ≥0.3(`TRACK_START_QUALITY_L3~L7`),质量不达标的本圈不起批、留在临时航迹表继续积累。
- **TAS 航迹与关联信息入库(5cb67cc)**:TAS 通道补齐数据记录——`track_asso_tas.cpp::model_output()` 在写出航迹后回填 `Trust_Track::Range/Azimuth/Elevation/Velocity` 并逐条记录到 `RdpRecTrustTrack`(记录范围由「仅 TWS」扩展为 TWS + TAS,两通道共用一张表);`model_filter()` 把原来的局部 `asso_info` 替换为统一的 `Asso_Info`,填充 `point_id`/`track_id`/`circle_id`/`delta_T`/`delta_h` 与门限量,并在「未通过门限的候选」「被取中候选及其余候选」两处写入 `RdpRecAssoInfo`。
- `Asso_Info` / `RdpRecAssoInfo` 新增 `tas_flag` 字段(5cb67cc,`struct.h`、`rdp_record_entity.h`、`rdp_record.cpp` 注册同步):取点迹的 `point_type`,用于在同一张关联信息表中区分 TWS(0)与 TAS(1)来源。
- `Trust_Track` 新增质量相关状态(1ae0cf2 / 4b70b24,`struct.h`):`Quality`(归一化航迹质量)、`Point_sum`(本条航迹累计点数,含外推圈)、`Valid_point_sum`(累计关联到实点的次数)、`Unupdated_time`(连续未关联实点的累计毫秒数);`Temp_track` 新增 `quality`。
### Changed
- **普通 TWS 航迹的消亡条件改为按航迹质量(782bff5,`track_die.cpp`)**:`Track_Mode==0 && primary_target_flag==0` 的航迹不再按外推圈数消亡,改为 `Quality < TRACK_DIE_QUALITY`(0.3)即消亡;**重点航迹仍按 `Extrapolate_round > TRACK_DIE_ROUND_PRIMARY`(10)**、**TAS 航迹仍按 `Extrapolate_round > TRACK_DIE_ROUND_TAS`**(相扫 10 / 机扫 96;相扫阈值其后于 5d105d2 调整为 20)消亡。因此 `TRACK_DIE_ROUND`(6)在 TWS 普通航迹上已不再被引用(宏仍在 `parameters.h` 中定义),普通航迹的存活时长改由质量衰减速度决定。
- 航迹质量递减策略两度调整并最终定为「按未更新时间二次衰减」(4b70b24、782bff5,`track_asso.cpp`):先由「按外推圈数线性递减」改为「按 `Unupdated_time` 线性递减」,再改为按未更新时长的二次函数累计——记 `t = Unupdated_time/1000`(秒),每次执行 `Quality += f(t_new) − f(t_old)`,其中 `f(t) = TRACK_QUALITY_DECREASE_COEFF_O2·t² + TRACK_QUALITY_DECREASE_COEFF_O1·t`(**系数取负值** −0.0008 / −0.0025),即未关联时间越长、每圈衰减越快,结果钳位到 ≥0。
- 外推的 ΔT 计算移到外推上限判断之外(4b70b24):未关联航迹的 `delta_T` 统一按 `work_mode` 取 `DATA_RATE_SHORT/MIDDLE/FAR` 计算,不再只在上限内计算,使 `Extrapolate_round` 超限后(不再预测、不再输出)仍能累计 `Unupdated_time` 并继续降低质量,直至触发质量消亡——这是「质量消亡取代圈数消亡」能够生效的前提。
- **扫描一圈完成判据按扫描体制区分(3e6763c,`data_process.cpp::data_preprocess()`)**:相扫(`PHASE_SCANNING`)恢复「前后两帧方位波位号比较」——当前帧 `Beam_index_aiz <= last_beam_num` 判为一圈完成返回 1,否则返回 2;机扫(`MECHANICAL_SCANNING`)保持 7d5a56f 起的「方位波位号 == 0」。两分支以 `#ifdef PHASE_SCANNING` 编译期隔离,`last_beam_num` 在相扫分支重新参与判断(初值 INT_MAX,故相扫首帧即返回 1)。
- TWS 外推时间差恢复为按数据率取固定值(56b5449 → b4aed8a,`track_asso.cpp::model_filter()`):56b5449 曾改为按 `latest_timestamp − T_track` 实时计算并对 `ΔT ≤ 0` 跳过,b4aed8a 回调为按 `work_mode` 取固定数据率(避免 TAS 工作时同步外推出 TWS 航迹);`track_asso_process()`/`model_filter()` 新增的 `latest_timestamp` 形参保留但当前不参与 ΔT 计算(实时计算代码以注释保留)。
- 临时航迹记录时机前移(e982804,`track_init.cpp::track_init_process_logic()`):`RdpRecTempTrack` 的写入由函数末尾(消亡之后)移到**转为可靠航迹之前**(清空点迹之后、`tmp_track_to_trust_track()` 之前),避免本圈起批的临时航迹被转走后在记录中丢失当前帧的临时航迹数据。
### Fixed
- 修正文档中「TWS 以方位波位号 == 0 判满圈」的过期描述(本次文档更新):判圈自 3e6763c 起按体制区分,相扫已恢复比较前后波位号并重新使用 `last_beam_num`;同步更正 README 2、7.2、8.1 节与 `docs/data_process_flow_report.md` 2.2 节、`docs/track_init_readme.md` 1.2 节。
- 修正文档中记录点覆盖范围的过期描述(本次文档更新):`RdpRecTrustTrack` / `RdpRecAssoInfo` 已覆盖 TWS + TAS 两通道(此前写作「仅 TWS」),临时航迹记录点位置已前移。
### Removed
- 无。
## [1.5.6] - 2026-10-08
> 本次发布把上一轮「工作区(未提交)」条目整体归档,并补充 a4f32e4 / c3c7e66 / b227ae1 三次提交的改动。
@@ -24,7 +134,7 @@
- **新增版本号输出(b227ae1)**:`parameters.h` 新增版本宏组——`MAJOR_VERSION` / `MINOR_VERSION` / `PATCH_VERSION`(当前 1 / 5 / 6)、扫描体制后缀 `VERSION_INFO`(相扫 `PS` / 机扫 `MS`)以及拼装宏 `VERSION_STRING`(`STRINGIFY` 两级宏展开,输出形如 `V1.5.6.PS`)。`Data_process_class_dll` 基类新增公开成员 `const char* version_info`,`Data_Process_Factory::GetB()` 创建实例后赋值为 `VERSION_STRING`;宿主可在拿到实例指针后直接读取该字段上报/核对 DLL 版本。
- **数据记录表扩充为 6 张在用表(7 个实体定义)(a4f32e4 / c3c7e66 / b227ae1)**:记录实体由「2 个示例」扩展为覆盖主要中间数据的 6 张在用表——`RdpRecPoint`(TWS/TAS 输入点迹,用 `point_type` 区分,逐点写入)、`RdpRecPointCohesionTWS` / `RdpRecPointCohesionTAS`(凝聚后点迹,分别写入)、`RdpRecTempTrack`(临时航迹,`track_init_process_logic()` 末尾在消亡临时航迹之后逐点写入)、`RdpRecTrustTrack`(可靠航迹,TWS 输出循环内按本圈更新航迹逐条写入)、`RdpRecAssoInfo`(点航关联信息,TWS 关联的候选/取中/删表三处写入)。原示例实体 `RdpRecTrackOut`(本圈输出航迹)与其在 `track_process()` 中的批量记录点已整体注释停用(实体定义保留注释,故 `rdp_record_entity.h` 中共 7 个实体定义),改由 `RdpRecTrustTrack` 记录内部真实航迹。
- **点迹/航迹记录新增 ID 与圈次维度(c3c7e66、b227ae1)**:`Data_Process` 新增自增成员 `point_id` / `frame_id` / `circle_id`(`data_process.h`)——每接收一帧 `frame_id` 自增;每个点迹 `point_id` 自增并随 `PointRecv` 落库;`circle_id` 在 TWS 满一圈(`Beam_index_aiz == 0`)、TAS 每帧处理结束时自增,写入该圈/帧的所有点迹。内部结构同步新增标识字段:`PointRecv` 增 `circle_id`;`Temp_track` 增 `temp_track_id` / `point_id` / `circle_id`(`Track_Init` 新增自增成员 `temp_track_id`);`Trust_Track` 增 `track_id` / `point_id` / `circle_id`(起批时取自临时航迹末点,关联成功时刷新为被关联点迹的 ID)。据此可把点迹、凝聚点迹、临时航迹、可靠航迹、关联信息按 `point_id` / `track_id` / `circle_id` 串成一条完整链路。
- **点迹/航迹记录新增 ID 与圈次维度(c3c7e66、b227ae1)**:`Data_Process` 新增自增成员 `point_id` / `frame_id` / `circle_id`(`data_process.h`)——每接收一帧 `frame_id` 自增;每个点迹 `point_id` 自增并随 `PointRecv` 落库;`circle_id` 在 TWS 满一圈、TAS 每帧处理结束时自增(TWS 判圈条件其后于 3e6763c 改为按体制区分),写入该圈/帧的所有点迹。内部结构同步新增标识字段:`PointRecv` 增 `circle_id`;`Temp_track` 增 `temp_track_id` / `point_id` / `circle_id`(`Track_Init` 新增自增成员 `temp_track_id`);`Trust_Track` 增 `track_id` / `point_id` / `circle_id`(起批时取自临时航迹末点,关联成功时刷新为被关联点迹的 ID)。据此可把点迹、凝聚点迹、临时航迹、可靠航迹、关联信息按 `point_id` / `track_id` / `circle_id` 串成一条完整链路。
- **新增 `Asso_Info` 关联信息结构体(b227ae1)**:原 `Track_Asso::model_filter()` 内定义的局部结构体 `asso_info` 提升为 `struct.h` 中的全局 `Asso_Info`,在原有 `d1/d2/d3/d_min/point_index/track_index` 基础上补充 `point_id` / `track_id` / `circle_id`、门限相关量(`delta_T` / `delta_h` / `R` / `VT`、三个布尔量 `thd_d` / `thd_h` / `thd_r`)以及「本候选是否被最近邻取中」标志 `min_d_flag`,使关联信息可被完整记录与分析。
- **可靠航迹补充极坐标量快照(b227ae1)**:`Trust_Track` 新增 `Range` / `Azimuth` / `Elevation` / `Velocity` 四个字段,在 TWS 输出环节由本圈写入输出结构的距离/方位/俯仰/合速度回填(`Velocity` 取 `Range_V`,即 IMM 融合速度的模),供记录表落库;注意它们只在航迹当圈**有输出**时被刷新。
- 新增 CHANGELOG.md,采用 Keep a Changelog 格式记录项目变更历史。
@@ -83,7 +193,7 @@
- 输出航迹改为「只输出本圈有更新的航迹」(50e0b5c):TWS `track_asso_process()` 与 TAS `track_asso_process_tas()` 的输出循环新增 `Track_Update_Flag == 1` 门控,输出后立即清零;该标志在关联到实点或外推成功时置 1。此前每个 TWS 周期都会把航迹表中的全部可靠航迹各输出一行(`Point_Sum = 1`),宿主不能再据输出行数推断航迹总数(当圈新起批的航迹仍由 `track_init` 的输出路径直接写入)。
- TWS 未关联航迹外推增加圈数上限(50e0b5c,`track_asso.cpp::model_filter()`):仅当 `Extrapolate_round <= MAX_EXTRAPOLATE_NUM`(=3)时按 `DATA_RATE_SHORT/MIDDLE/FAR` 预测一帧、推进航迹时间并置 `Track_Update_Flag = 1`;超过上限后不再推进位置与时间,只累加 `Extrapolate_round`、置 `point_flag = 0` 与 `Track_Update_Flag = 0`(该航迹此后不再输出,位置冻结在最后一次预测处),直到重新关联到实点才恢复。避免长时间未关联的航迹被反复外推到远处。
- TAS 未关联航迹外推同样增加上限(50e0b5c,`track_asso_tas.cpp`),但判定方向与 TWS 相反:`Extrapolate_round >= MAX_EXTRAPOLATE_NUM_TAS`(相扫 5 / 机扫 48)时才做预测、推进航迹时间并输出,未达该轮数时只累加 `Extrapolate_round` 且不输出(位置与航迹时间停在最后一次关联处),达到上限后才恢复逐帧外推输出(见 README 11.2 的观察)。
- 航迹消亡圈数与判据调整(50e0b5c,`parameters.h`、`track_die.cpp`、`track_die_tas.cpp`):`TRACK_DIE_ROUND` 由 4 调整为 6(两体制),新增重点航迹专用 `TRACK_DIE_ROUND_PRIMARY = 10`,`TRACK_DIE_ROUND_TAS` 相扫由 5 调整为 10(机扫保持 96);消亡判据由 `Extrapolate_round >= 阈值` 改为 `> 阈值`,并把 `manual_delete_flag == 1` 与轮数条件合并为统一的 `die_flag` 判定,TWS 侧按 `primary_target_flag` 在 6 / 10 之间二选一。因每圈未关联只自增 1,普通 TWS 航迹实际连续 7 圈无实点关联才消亡(重点航迹 11 圈,TAS 相扫 11 圈 / 机扫 97 圈)。
- 航迹消亡圈数与判据调整(50e0b5c,`parameters.h`、`track_die.cpp`、`track_die_tas.cpp`):`TRACK_DIE_ROUND` 由 4 调整为 6(两体制),新增重点航迹专用 `TRACK_DIE_ROUND_PRIMARY = 10`,`TRACK_DIE_ROUND_TAS` 相扫由 5 调整为 10(机扫保持 96);消亡判据由 `Extrapolate_round >= 阈值` 改为 `> 阈值`,并把 `manual_delete_flag == 1` 与轮数条件合并为统一的 `die_flag` 判定,TWS 侧按 `primary_target_flag` 在 6 / 10 之间二选一。因每圈未关联只自增 1,普通 TWS 航迹实际连续 7 圈无实点关联才消亡(重点航迹 11 圈,TAS 相扫 11 圈 / 机扫 97 圈)。(普通 TWS 航迹的该判据于 1.5.8 改为质量消亡,见 1.5.8 区块。)
- 「靠近/远离」方向标志 `Flag_Direction` 改为按滤波速度矢量判定(fc49707 引入、42b598e 调整,`track_asso.cpp` 与 `track_asso_tas.cpp` 同步):不再使用受多普勒速度模糊影响、符号可能取反的实测径向速度(原 `Flag_Direction = vr_point > 0 ? 1 : 0`),改为用航迹 IMM 融合状态的速度方向角 `Direction_Angle = atan2(X[4], X[1])` 与位置矢量判定:`acos((-x·cos(Direction_Angle) − y·sin(Direction_Angle)) / r) < π/2` 时置 1(靠近),否则 0(远离),其中 `x`/`y` 为经 `Sys_delay` 补偿后的位置、`r` 为其模。该判据等价于「滤波速度矢量的径向分量指向雷达(径向接近)即判为靠近」;中间版本曾按合速度归一化并把门限取为 π/4(45° 锥角),42b598e 去掉归一化并把门限放宽到 π/2(90°)。
- 手动操作接口遍历命中目标后立即 `break`(fc49707,`data_process.cpp`):`primary_target_add` / `primary_target_cancel` / `tracking_start` / `tracking_stop` 在写入标志后退出循环,不再继续扫描整张航迹表(批号唯一,结果等价)。
- `requirements.md` 由 `archived_docs/` 移回仓库根目录(工作区改动,文件内容未变):README 目录结构(第 3 节)与参考文档表(第 13 节)中的引用路径同步改回 `requirements.md`。
+98 -13
View File
@@ -10,16 +10,10 @@ cmake_minimum_required(VERSION 3.21)
# 因此本工程的最低 CMake 版本随之提高;NMake/Ninja 生成器同样满足。
project(data_process_class_dll LANGUAGES CXX)
# 扫描模式:
# PHASE_SCANNING -> 相扫雷达(X256_PS,原 Qt 工程默认激活配置)
# MECHANICAL_SCANNING -> 机扫雷达(X256_MS)
# 用法:cmake -S . -B build -DRADAR_SCAN_MODE=PHASE_SCANNING
set(RADAR_SCAN_MODE "PHASE_SCANNING" CACHE STRING "Radar scan mode (PHASE_SCANNING or MECHANICAL_SCANNING)")
set_property(CACHE RADAR_SCAN_MODE PROPERTY STRINGS PHASE_SCANNING MECHANICAL_SCANNING)
if(NOT RADAR_SCAN_MODE STREQUAL "PHASE_SCANNING" AND NOT RADAR_SCAN_MODE STREQUAL "MECHANICAL_SCANNING")
message(FATAL_ERROR "RADAR_SCAN_MODE 必须为 PHASE_SCANNING 或 MECHANICAL_SCANNING,当前值:${RADAR_SCAN_MODE}")
endif()
# 机扫 / 相扫版本已合并为同一份 DLL:构建不再区分扫描体制,也不再定义
# PHASE_SCANNING / MECHANICAL_SCANNING 编译期宏,也没有 RADAR_SCAN_MODE 缓存变量。
# 扫描模式改由运行期配置项的 `[radar_params] scan_mode` 决定(0 相扫 / 1 机扫),
# 随全部雷达工作参数一起由 TOML 配置文件经 parameters_reload() 载入,详见 README 4.2、10。
# ---------------------------------------------------------------------------
# 数据记录调试功能开关(可选编译,详见 docs/data_record_debug.md)
@@ -56,6 +50,51 @@ else()
message(STATUS "数据记录调试功能:关闭(不编译记录模块,RDP_RECORD 为空操作)")
endif()
# ---------------------------------------------------------------------------
# 日志编译期级别(spdlog)
# 原 parameters.h 的 RDP_LOG_ENABLE 布尔开关已移除:日志级别改由 spdlog 的
# SPDLOG_ACTIVE_LEVEL 在编译期控制(低于该级别的 RDP_LOG_* 日志在编译期被
# 剔除、实参不求值,OFF 即等价于原「关闭日志模块」)。本缓存变量把级别映射
# 到 SPDLOG_ACTIVE_LEVEL,并同时用于 data_process_class_dll 与 rdp_record
# 两个目标(两个目标使用同一份 spdlog 头文件配置,必须保持一致)。
# AUTO : Debug 构建 = TRACE,其余构建 = INFO(默认)
# TRACE / DEBUG / INFO / WARN / ERROR / CRITICAL : 显式指定最低编译级别
# OFF : 编译期关闭全部 RDP_LOG_* 日志(rdp_log::Init() 也不再创建日志文件)
# 修改方式:cmake -DRDP_LOG_LEVEL=WARN ...(或在 CMakeCache 中修改后重新配置)
# ---------------------------------------------------------------------------
set(RDP_LOG_LEVEL "AUTO" CACHE STRING "日志编译期级别:AUTO(Debug=TRACE/其它=INFO) / TRACE / DEBUG / INFO / WARN / ERROR / CRITICAL / OFF")
set_property(CACHE RDP_LOG_LEVEL PROPERTY STRINGS AUTO TRACE DEBUG INFO WARN ERROR CRITICAL OFF)
if(RDP_LOG_LEVEL STREQUAL "AUTO")
if(CMAKE_CONFIGURATION_TYPES)
set(RDP_LOG_ACTIVE_LEVEL_DEFS
"$<$<CONFIG:Debug>:SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE>"
"$<$<NOT:$<CONFIG:Debug>>:SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_INFO>")
set(RDP_LOG_LEVEL_DESC "AUTO(多配置生成器:Debug=TRACE,其它=INFO)")
elseif(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(RDP_LOG_ACTIVE_LEVEL_DEFS "SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE")
set(RDP_LOG_LEVEL_DESC "AUTO(Debug 构建 → TRACE)")
else()
set(RDP_LOG_ACTIVE_LEVEL_DEFS "SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_INFO")
if(CMAKE_BUILD_TYPE)
set(RDP_LOG_LEVEL_DESC "AUTO(${CMAKE_BUILD_TYPE} 构建 → INFO)")
else()
set(RDP_LOG_LEVEL_DESC "AUTO(未指定构建类型 → INFO)")
endif()
endif()
elseif(RDP_LOG_LEVEL STREQUAL "OFF")
set(RDP_LOG_ACTIVE_LEVEL_DEFS "SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_OFF")
set(RDP_LOG_LEVEL_DESC "OFF(编译期关闭全部 RDP_LOG_* 日志)")
elseif(RDP_LOG_LEVEL MATCHES "^(TRACE|DEBUG|INFO|WARN|ERROR|CRITICAL)$")
set(RDP_LOG_ACTIVE_LEVEL_DEFS "SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_${RDP_LOG_LEVEL}")
set(RDP_LOG_LEVEL_DESC "${RDP_LOG_LEVEL}")
else()
message(FATAL_ERROR
"RDP_LOG_LEVEL 必须为 AUTO / TRACE / DEBUG / INFO / WARN / ERROR / CRITICAL / OFF,"
"当前值:${RDP_LOG_LEVEL}")
endif()
message(STATUS "日志编译期级别:${RDP_LOG_LEVEL_DESC}(映射为 SPDLOG_ACTIVE_LEVEL)")
# ---------------------------------------------------------------------------
# 语言标准:C++17
# ---------------------------------------------------------------------------
@@ -89,10 +128,44 @@ if(NOT CMAKE_GENERATOR MATCHES "Visual Studio")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
endif()
# ---------------------------------------------------------------------------
# 内置第三方库 spdlog(日志,v1.17.0,见 third_party/spdlog/UPSTREAM.md)
# 以静态库方式编译(SPDLOG_COMPILED_LIB),供 data_process_class_dll 与
# rdp_record(C++20 静态库,rdp_record.cpp 内也有日志)链接:
# - PUBLIC 编译定义 SPDLOG_COMPILED_LIB / SPDLOG_ACTIVE_LEVEL 与头文件
# 目录,保证所有使用方(含跨 C++17/C++20 的两目标)配置一致;
# - SPDLOG_ACTIVE_LEVEL 由上方 RDP_LOG_LEVEL 决定;
# - 第三方源码以 /w 编译,维持本项目 /W3 零告警约定(见 README 4.5)。
# ---------------------------------------------------------------------------
set(SPDLOG_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/third_party/spdlog")
add_library(spdlog STATIC
third_party/spdlog/src/spdlog.cpp
third_party/spdlog/src/async.cpp
third_party/spdlog/src/cfg.cpp
third_party/spdlog/src/color_sinks.cpp
third_party/spdlog/src/file_sinks.cpp
third_party/spdlog/src/stdout_sinks.cpp
third_party/spdlog/src/bundled_fmtlib_format.cpp
)
target_include_directories(spdlog PUBLIC ${SPDLOG_ROOT}/include)
target_compile_definitions(spdlog PUBLIC
SPDLOG_COMPILED_LIB
${RDP_LOG_ACTIVE_LEVEL_DEFS}
)
if(MSVC)
target_compile_options(spdlog PRIVATE /w /utf-8 /Zc:__cplusplus /Zc:wchar_t /FS)
endif()
set_target_properties(spdlog PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
)
message(STATUS "第三方日志库:spdlog v1.17.0(静态库,third_party/spdlog)")
set(SOURCES
data_process_class_dll/rdp_log.cpp
data_process_class_dll/data_process_class_dll.cpp
data_process_class_dll/data_process.cpp
data_process_class_dll/rdp_parameter_loader.cpp
data_process_class_dll/dot_coh.cpp
data_process_class_dll/kalman.cpp
data_process_class_dll/coor_trans.cpp
@@ -114,6 +187,11 @@ set(PUBLIC_HEADERS
add_library(data_process_class_dll SHARED ${SOURCES})
# 日志库(静态库 spdlog):
# 目标级 PUBLIC 接口提供 spdlog 头文件目录、SPDLOG_COMPILED_LIB 与 SPDLOG_ACTIVE_LEVEL,
# 使 rdp_log.cpp 及各业务模块(RDP_LOG_* 宏)以一致的配置编译。
target_link_libraries(data_process_class_dll PRIVATE spdlog)
# ---------------------------------------------------------------------------
# 数据记录模块(RDP_DATA_RECORD 启用时构建):独立静态库、C++20(ormpp 要求),
# 与雷达处理代码(C++17)通过 rdp_record.h 的「仅声明 + 显式实例化」跨标准边界。
@@ -144,9 +222,11 @@ if(RDP_RECORD_ENABLED)
${CMAKE_CURRENT_SOURCE_DIR}/third_party/ormpp/thirdparty/sqlite3
)
target_compile_options(rdp_record PRIVATE
/W3
# /W3 与 /Zc:__cplusplus 只用于 C++ 源文件:sqlite3.c 由下方源文件级 /w 关闭告警,
# 若 C 文件也带 /W3,cl 会打印 D9025(overriding '/W3' with '/w')命令行提示。
$<$<COMPILE_LANGUAGE:CXX>:/W3>
/utf-8
/Zc:__cplusplus
$<$<COMPILE_LANGUAGE:CXX>:/Zc:__cplusplus>
/FS
)
@@ -154,13 +234,16 @@ if(RDP_RECORD_ENABLED)
set_source_files_properties(third_party/ormpp/thirdparty/sqlite3/sqlite3.c
PROPERTIES COMPILE_OPTIONS "/w")
# rdp_record.cpp 内的 RDP_LOG_* 日志经 rdp_log.h 使用 spdlog:
# 链接 spdlog 目标以获得其 PUBLIC 头文件目录与 SPDLOG_COMPILED_LIB /
# SPDLOG_ACTIVE_LEVEL 定义(与 DLL 目标保持一致)。
target_link_libraries(rdp_record PRIVATE spdlog)
target_link_libraries(data_process_class_dll PRIVATE rdp_record)
endif()
# 编译 DLL 时定义导出宏;引用 DLL 的工程不应定义该宏。
target_compile_definitions(data_process_class_dll PRIVATE
DATA_PROCESS_CLASS_DLL_LIBRARY
${RADAR_SCAN_MODE}
)
# 与 qmake 工程保持一致的 Windows 编译定义
@@ -175,8 +258,10 @@ if(WIN32)
endif()
# Eigen 头文件位于源码目录下(<Eigen/Dense>)。
# toml++ 为单头文件第三方库(<toml.hpp>,仅 rdp_parameter_loader.cpp 使用)。
target_include_directories(data_process_class_dll PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/data_process_class_dll
${CMAKE_CURRENT_SOURCE_DIR}/third_party/toml
)
if(MSVC)
+515 -203
View File
File diff suppressed because it is too large Load Diff
+163
View File
@@ -0,0 +1,163 @@
# 雷达数据处理配置文件(TOML 格式)
#
# 说明:
# 1) 配置段与 struct.h::RDP_parameter 的注释分组一一对应,配置键名与结构体成员名完全一致;
# 2) 所有标量键均为必填项,且会做类型与取值范围校验(详见 rdp_parameter_loader.cpp);
# 3) 屏蔽区数组使用与结构体成员同名的 TOML 数组,元素个数必须等于对应 *_num(0~30);
# *_num = 0 时数组可省略,存在时必须为空数组。
## 雷达工作参数
[radar_params]
# 工作模式:0 近程 / 1 中程 / 2 远程
radar_work_mode = 0
# 扫描模式:0 相扫模式 / 1 机扫模式
scan_mode = 0
# 频点0频率(GHz)
freq_0_GHz = 9.2
# 雷达平台高度(m)
radar_height = 0
# 系统延迟(s)
radar_system_delay = 0
# TWS模式数据率(s)
data_rate_tws = 3
# TAS模式数据率(s)
data_rate_tas = 1
# 纯跟踪模式数据率(s)
data_rate_track_only = 0.2
## 点迹凝聚参数
[cohesion]
# 距离凝聚范围(m)
point_cohesion_range = 80
# 速度凝聚范围(m/s)
point_cohesion_velocity = 2
# 方位凝聚范围(度)
point_cohesion_azimuth = 6
# 最小凝聚距离(m)
point_cohesion_range_min = 100
# 最大凝聚距离(m)
point_cohesion_range_max = 100000
## 航迹关联参数
[track_associate]
# 距离量测精度(m)
sigma_range = 10.0
# 方位量测精度(弧度)
sigma_azimuth = 0.02
# 俯仰量测精度(弧度)
sigma_pitch = 0.2
# 速度量测精度(m/s)
sigma_velocity = 2.0
# 关联门限
associate_threshold = 4.0
# 最大外推点数
extrapolate_max = 3
# TAS最大外推点数
extrapolate_max_tas = 5
# 高度平滑窗长
height_smooth_win = 6
## 航迹起始参数
[track_init]
# 起航波门
track_init_threshold = 8
# 起航夹角(度)
track_init_angle = 60
# 最少起批点数(3~9)
track_init_point_num_min = 3
# 3点起批质量门限
track_init_quality_L3 = 0.7
# 4点起批质量门限
track_init_quality_L4 = 0.6
# 5点起批质量门限
track_init_quality_L5 = 0.5
# 6点起批质量门限
track_init_quality_L6 = 0.4
# 7点起批质量门限
track_init_quality_L7 = 0.3
# 航迹起始最小速度(m/s)
track_init_velocity_min = 1
# 航迹起始最大速度(m/s)
track_init_velocity_max = 50
## 航迹消亡参数
[track_die]
# 航迹消亡圈数
track_die_round = 6
# 重点航迹消亡圈数
track_die_round_pramary = 10
# TAS航迹消亡圈数
track_die_round_tas = 20
# 航迹消批质量条件
track_die_quality = 0.3
# 重点航迹消批质量条件
track_die_quality_pramary = 0.3
## 航迹质量管理参数
[track_quality]
# 航迹质量最大值
track_quality_max = 100
# 航迹质量最小值
track_quality_min = 0
# 马氏距离权重
track_quality_d_weight = 0.5
# 信噪比权重
track_quality_snr_weight = 0.3
# 航迹长度权重
track_quality_len_weight = 0.2
# 航迹质量迭代系数
track_quality_iter_coeff = 0.8
# 航迹质量下降系数2次项(必须 <= 0)
track_quality_decrease_coeff_o2 = -0.0008
# 航迹质量下降系数1次项(必须 <= 0)
track_quality_decrease_coeff_o1 = -0.0025
## 航迹批号管理
[track_index]
# 最大航迹批号(1~500)
track_index_max = 500
## 波束调度
[beam_control]
# 默认跟踪波束类型
default_track_beam_type = 1
# 最大同时跟踪目标数(TAS 队列容量上限 30)
tas_track_num_max = 4
## singer模型机动参数(均必须 > 0)
[singer_model]
# 模型1
model_1_Q_fast = 0.000001
model_1_Q_slow = 0.000001
# 模型2
model_2_Q_fast = 0.01
model_2_Q_slow = 0.01
# 模型3
model_3_Q_fast = 1.0
model_3_Q_slow = 1.0
## 航迹起始屏蔽区(数组元素个数必须等于 track_init_prohibite_area_num)
[track_init_prohibite_area]
# 航迹起始屏蔽区数(0~30)
track_init_prohibite_area_num = 0
# 最小距离 (m)
track_init_prohibite_area_range_min = []
# 最大距离 (m)
track_init_prohibite_area_range_max = []
# 最小方位角 (度)
track_init_prohibite_area_azimuth_min = []
# 最大方位角 (度)
track_init_prohibite_area_azimuth_max = []
## 禁止TAS跟踪区域(数组元素个数必须等于 tas_prohibite_area_num)
[tas_prohibite_area]
# 禁止TAS的区域数(0~30)
tas_prohibite_area_num = 0
# 最小距离 (m)
tas_prohibite_area_range_min = []
# 最大距离 (m)
tas_prohibite_area_range_max = []
# 最小方位角 (度)
tas_prohibite_area_azimuth_min = []
# 最大方位角 (度)
tas_prohibite_area_azimuth_max = []
+66 -38
View File
@@ -1,13 +1,17 @@
#include "data_process.h"
#include "rdp_log.h"
#include "rdp_record.h"
#include "rdp_record_entity.h"
#include "rdp_parameter_loader.h"
#include <cstring>
#include <string>
#include <vector>
#include <cmath>
#include <iostream>
using namespace std;
// 全局工作参数对象(声明见 struct.h)。所有处理模块直接访问,由 parameters_reload() 加载。
RDP_parameter rdp_parameter{};
/*******************************************************************************/
/*******************************************************************************/
/************************************用户接口函数*******************************/
@@ -26,6 +30,11 @@ int Data_Process::data_preprocess(struct DataRev Data_Input[150])
{
data_num=min(Data_Input[0].Point_Sum, 150);
//std::cout << "TARGET azi beam idx:" <<Data_Input[0].Beam_index_aiz;
#ifdef RDP_RECORD_ENABLE
//记录输入点迹:处理线程内先收集源结构体,循环结束后一次入队(构造实体与写库由记录写线程完成)
std::vector<PointRecv> rec_points;
rec_points.reserve(static_cast<std::size_t>(data_num));
#endif
for (int i=0; i<data_num;i++)
{
PointRecv Data_buffer_temp;
@@ -54,37 +63,47 @@ int Data_Process::data_preprocess(struct DataRev Data_Input[150])
std::memcpy(Data_buffer_temp.speed_dim, Data_Input[i].speed_dim, sizeof(Data_buffer_temp.speed_dim));
std::memcpy(Data_buffer_temp.range_dim, Data_Input[i].range_dim, sizeof(Data_buffer_temp.range_dim));
//数据记录示例①:把本次接收的输入点迹写入表 RdpRecPointTws(字段为示例,可按需增删)。
//记录功能关闭时该语句展开为空操作(实参不求值),交付构建不产生任何代码,
//用法与新增记录点配方见 rdp_record.h 与 docs/data_record_debug.md。
RDP_RECORD(RdpRecPoint(Data_buffer_temp));
#ifdef RDP_RECORD_ENABLE
rec_points.push_back(Data_buffer_temp);
#endif
Data_buffer.push_back(Data_buffer_temp);
}
// 修改一周扫描完成的判断条件,原来采用判断波位号变小为一周扫描完成,但机扫模式下,可能存在转台反转的情况,导致波位号变小,但并未扫描一周,因此改为判断波位号为0时,表示扫描一周完成
if(Data_Input[0].Beam_index_aiz == 0) //TWS
{
#ifdef RDP_RECORD_ENABLE
//判圈之前(所有 return 之前)把本帧收集的点迹一次入队
RDP_RECORD_POINTS(rec_points);
#endif
// 判圈:扫描模式由配置参数 scan_mode 决定(0=相扫,1=机扫),替代原编译期 PHASE_SCANNING/MECHANICAL_SCANNING 分支。
// 相扫:判断前后两帧波位号的大小,来判断是否扫描一周完成(当前波位号 <= 上一帧波位号即判满圈);
// 机扫:可能存在转台反转导致波位号变小但并未扫描一周,因此改为波位号为 0 时判满圈。
const bool circle_done = (rdp_parameter.scan_mode == 0)
? (Data_Input[0].Beam_index_aiz <= last_beam_num) //TWS 相扫
: (Data_Input[0].Beam_index_aiz == 0); //TWS 机扫
last_beam_num = Data_Input[0].Beam_index_aiz;
if(circle_done)
{
circle_id++;
// std::cout << Data_Input[0].CPI_time << std::endl;
// RDP_LOG<<"CPI_time: "<<Data_Input[0].CPI_time<<std::endl;
// RDP_LOG_DEBUG("CPI_time: {}", Data_Input[0].CPI_time);
return 1;
}
else
{
last_beam_num = Data_Input[0].Beam_index_aiz;
return 2;
}
}
else if(Data_Input[0].point_type == 1) //tas数据
{
// std::cout << "TAS TARGET :" <<Data_Input[0].TAS_track_index;
//std::cout << "TAS time :" <<Data_Input[0].CPI_time;
data_num = min(max(Data_Input[0].Point_Sum, 0), 150);
TAS_track_idx = Data_Input[0].TAS_track_index;
TAS_track_fingerprint = Data_Input[0].TAS_track_index;
#ifdef RDP_RECORD_ENABLE
//记录输入点迹:处理线程内先收集源结构体,循环结束后一次入队(构造实体与写库由记录写线程完成)
std::vector<PointRecv> rec_points;
rec_points.reserve(static_cast<std::size_t>(data_num));
#endif
for (int i=0; i<data_num;i++)
{
PointRecv Data_buffer_temp;
@@ -113,7 +132,9 @@ int Data_Process::data_preprocess(struct DataRev Data_Input[150])
std::memcpy(Data_buffer_temp.speed_dim, Data_Input[i].speed_dim, sizeof(Data_buffer_temp.speed_dim));
std::memcpy(Data_buffer_temp.range_dim, Data_Input[i].range_dim, sizeof(Data_buffer_temp.range_dim));
RDP_RECORD(RdpRecPoint(Data_buffer_temp));
#ifdef RDP_RECORD_ENABLE
rec_points.push_back(Data_buffer_temp);
#endif
Data_buffer_tas.push_back(Data_buffer_temp);
@@ -124,6 +145,10 @@ int Data_Process::data_preprocess(struct DataRev Data_Input[150])
// << "data_num:" << data_num;
}
//circle_id++ 之前把本帧收集的点迹一次入队
#ifdef RDP_RECORD_ENABLE
RDP_RECORD_POINTS(rec_points);
#endif
circle_id++;
return 3;
}
@@ -147,7 +172,7 @@ int model)
if(model==2)
{
dot_coh.dot_coh_process_buff(&Data_buffer,&point_recv,Work_Parameter);
dot_coh.dot_coh_process_buff(&Data_buffer,&point_recv);
std::vector<PointRecv>().swap(Data_buffer);
}
@@ -160,7 +185,7 @@ int model)
&trust_track,
Trust_Track_Output,
Trust_track_num_Output,
Work_Parameter
latest_timestamp
);
track_die.track_die_process( &trust_track,
@@ -171,8 +196,7 @@ int model)
&trust_track,
&temp_track,
Trust_Track_Output,
Trust_track_num_Output,
Work_Parameter);
Trust_track_num_Output);
}
@@ -182,6 +206,17 @@ int model)
*Trust_track_num_Output=0; //航迹更新数
*Track_die_num_Output=0; //航迹消亡数目
//查找指定指纹的Track_idx
int tas_track_idx = -1;
for(auto& t : trust_track){
if(t.Track_fingerprint == TAS_track_fingerprint){
tas_track_idx = t.Track_Index;
t.manual_tracking_flag = 1;
t.Track_Mode = 1;
break;
}
}
dot_coh_tas.dot_coh_tas_process(&Data_buffer_tas,&point_recv_tas); //点迹凝聚
std::vector<PointRecv>().swap(Data_buffer_tas);//凝聚完毕 清除Data_buffer
@@ -190,8 +225,7 @@ int model)
&trust_track, //航迹文件
Trust_Track_Output, //更新航迹信息
Trust_track_num_Output, //更新航迹数
Work_Parameter, //工作参数
TAS_track_idx,
tas_track_idx,
latest_timestamp //最新时间戳
);
@@ -213,27 +247,21 @@ void Data_Process::Beam_Ctrl(struct TrackingBeam *Tracking_beam,
int *Trust_track_num_Output)
{
tas_ctrl.tas_ctrl_process(&trust_track,Tracking_beam, Trust_Track_Output, Trust_track_num_Output,Work_Parameter, latest_timestamp);
tas_ctrl.tas_ctrl_process(&trust_track,Tracking_beam, Trust_Track_Output, Trust_track_num_Output, latest_timestamp);
}
//数据处理参数初始化
int Data_Process::track_process_parameters_initial(struct RadarPara Radar_Parameter)
//数据处理参数加载(从 TOML 配置文件加载全部工作参数到全局 rdp_parameter)
int Data_Process::parameters_reload(const char* parameter_file_path)
{
//工作参数设置
if (Radar_Parameter.track_start_point_num < 3 || Radar_Parameter.track_start_point_num > 9)
std::string error;
// rdp_parameter_load 仅在完整校验通过后写入目标对象,失败时保持原参数不变
if (rdp_parameter_load(parameter_file_path, rdp_parameter, &error) != 0)
{
RDP_LOG_ERROR("parameters_reload failed: {}", error);
return -1;
memcpy(&Work_Parameter, &Radar_Parameter, sizeof(RadarPara));
return 0;
}
//数据处理参数修改
int Data_Process::track_process_parameters_modify(struct RadarPara Radar_Parameter)
{
if (Radar_Parameter.track_start_point_num < 3 || Radar_Parameter.track_start_point_num > 9)
return -1;
memcpy(&Work_Parameter, &Radar_Parameter, sizeof(RadarPara));
RDP_LOG_INFO("parameters_reload ok: {}", parameter_file_path != nullptr ? parameter_file_path : "");
return 0;
}
@@ -271,7 +299,7 @@ int Data_Process:: track_delete(int delete_track_num, //手动
{
trust_track[j].manual_delete_flag=1;
// RDP_LOG << "Delete :" << trust_track[j].Track_Index << std::endl;
// RDP_LOG_DEBUG("Delete : {}", trust_track[j].Track_Index);
}
}
+6 -8
View File
@@ -68,11 +68,10 @@ public:
//手动打跟踪波束
int tracking_point(float Azimuth); //方位角
//参数设置初始化
int track_process_parameters_initial(struct RadarPara Radar_Parameter);
//参数设置修改函数
int track_process_parameters_modify(struct RadarPara Radar_Parameter);
//参数加载函数(从 TOML 配置文件加载全部工作参数到 rdp_parameter)
//参数:parameter_file_path - 配置文件路径
//返回:成功返回 0;失败返回 -1(失败时不改变当前参数)
int parameters_reload(const char* parameter_file_path) override;
private:
@@ -84,13 +83,14 @@ private:
std::vector <Trust_Track> trust_track; //可靠航迹 按航迹区存入
std::vector <std::vector<Temp_track>> temp_track; //临时航迹 按临时航迹区存入
// 工作参数为全局对象 struct.h::rdp_parameter(由 parameters_reload() 加载),不再作为类成员。
int Beam_num; //波位数
int beam_count; //波位计数
int data_num; //波位1点数
int beam_index[3]; //3个波位号(当前帧)
int TAS_track_idx; //TAS数据的目标批号
int TAS_track_fingerprint; //TAS数据的目标批号
int Track_Mode; //跟踪模式 1TAS 0TWS
@@ -104,8 +104,6 @@ private:
long long circle_id = 0; //圈id
struct RadarPara Work_Parameter; //工作参数 参数设置函数外部输入
Dot_Coh dot_coh; //点迹凝聚
Dot_Coh_TAS dot_coh_tas;
@@ -19,8 +19,8 @@ Data_process_class_dll *Data_Process_Factory::GetB()
{
if(!p)
{
//初始化日志输出:RDP_LOG_ENABLE 开启时重定向 std::cout 到日志文件
//rdp_log::Init();
//初始化日志输出:标准输出 + 日志文件(.\rdp_logs\rdp_YYYYMMDD_HHMMSS.log)
rdp_log::Init();
//初始化数据记录:RDP_DATA_RECORD 开启时创建记录目录、打开本次运行的记录库;
//关闭时 rdp_record::Init() 为空实现,不产生任何开销
@@ -42,10 +42,10 @@ void Data_Process_Factory::Destroy()
delete p;
p = nullptr;
//恢复 std::cout 到控制台并关闭日志文件
//rdp_log::Shutdown();
//关闭记录库(RDP_DATA_RECORD 关闭时为空实现)
rdp_record::Shutdown();
//flush 并关闭日志文件(须在记录模块关闭之后:其 Shutdown 仍会写日志)
rdp_log::Shutdown();
}
}
@@ -51,6 +51,7 @@ struct DATA_PROCESS_CLASS_DLLSHARED_EXPORT Track
float Elevation; //俯仰
float Range_V; //速度
float Amplitude; //幅度
float Quality; //航迹质量
int Track_Index; //航迹号
int Point_Sum; //本条航迹更新点的数目
int Flag_Direction; //方向标志1靠近/0远离
@@ -108,70 +109,6 @@ struct DATA_PROCESS_CLASS_DLLSHARED_EXPORT Target_direct_tracking
int open_flag; // 1 引导跟踪 0 结束引导跟踪
};
// 雷达参数结构体
struct DATA_PROCESS_CLASS_DLLSHARED_EXPORT RadarPara
{
int Beam_num; //波位数
float Height; //雷达平台高度 (m)
float Sys_delay; //系统延迟 (s)
float R_TAS_max; //TAS最大距离 (m)
float R_TAS_min; //TAS最小距离 (m)
float V_TAS_max; //TAS最大速度 (m/s)
float V_TAS_min; //TAS最小速度 (m/s)
float TAS_Height_1; //TAS跟踪高度1 (m)
float TAS_Height_2; //TAS跟踪高度2 (m)
float TAS_Height_3; //TAS跟踪高度3 (m)
float TAS_Height_4; //TAS跟踪高度4 (m)
float TAS_Height_5; //TAS跟踪高度5 (m)
int track_prohibite_area_num; //禁止航迹起始的区域数目
float R_max_track_prohibited[30]; //最大距离 (m)
float R_min_track_prohibited[30]; //最小距离 (m)
float Azimuth_max_track_prohibited[30]; //最大方位角 (度)
float Azimuth_min_track_prohibited[30]; //最小方位角 (度)
int TAS_prohibite_area_num; //禁止TAS的区域数目
float R_max_TAS_prohibited[30]; //最大距离 (m)
float R_min_TAS_prohibited[30]; //最小距离 (m)
float Azimuth_max_TAS_prohibited[30]; //最大方位角 (度)
float Azimuth_min_TAS_prohibited[30]; //最小方位角 (度)
int cfar_th; //cfar门限
int work_mode; //工作模式 0进程 1中程 2远程
int north_angle; //北偏角
float V_MAX; //最大速度
float V_MIN; //最小速度
float DATA_RATE_SHORT; //三种不同模式下的 数据率
float DATA_RATE_MIDDLE;
float DATA_RATE_FAR;
float DATA_RATE_TAS; //TAS模式数据率,由dll调用者输入;TAS波束输出间隔阈值为 DATA_RATE_TAS 秒
//数据关联参数
int track_start_point_num; //起批点数(典型值 3或4)
float track_start_threshold; //起航波门大小(典型值 3)
float track_asso_threshold; //关联波门大小(典型值 3)
float track_asso_threshold_tas; //关联波门大小tas (典型值 3)
//singer模型机动参数
float Model1_Q_fast; //0.00001
float Model1_Q_slow; //0.00001
float Model2_Q_fast; //0.1
float Model2_Q_slow; //0.001
float Model3_Q_fast; //0.8
float Model3_Q_slow; //0.4
};
class DATA_PROCESS_CLASS_DLLSHARED_EXPORT Data_process_class_dll
{
@@ -260,16 +197,12 @@ public:
return 0;
}
//参数设置初始化函数
virtual int track_process_parameters_initial(struct RadarPara Radar_Parameter)
//数据处理参数加载函数(从 TOML 配置文件加载全部工作参数)
//参数:parameter_file_path - 配置文件路径(宿主进程当前工作目录可达的路径)
//返回:成功返回 0;失败返回 -1(失败时不改变当前参数)
virtual int parameters_reload(const char*)
{
return 0;
}
//参数设置修改函数
virtual int track_process_parameters_modify(struct RadarPara Radar_Parameter)
{
return 0;
return -1;
}
};
+25 -18
View File
@@ -3,13 +3,11 @@
#include <cstring>
#include <vector>
#include "rdp_record.h"
#include "rdp_record_entity.h"
using namespace std;
int Dot_Coh::dot_coh_process(std::vector <PointRecv> *data_input,
std::vector <PointRecv> *point_recv,
struct RadarPara Work_Parameter)
std::vector <PointRecv> *point_recv)
{
if(data_input->size()>1)
@@ -33,10 +31,10 @@ int Dot_Coh::dot_coh_process(std::vector <PointRecv> *data_input,
float point_1_A=static_cast<float>((*data_input)[i].amplitude);
//凝聚条件: 距离、方位接近
if(Work_Parameter.work_mode == 0)
if(rdp_parameter.radar_work_mode == 0)
{
float delta_F = fabs(point_0_F-point_1_F)<2*PI-fabs(point_0_F-point_1_F) ? fabs(point_0_F-point_1_F) :2*PI-fabs(point_0_F-point_1_F);
if( (fabs(point_0_R-point_1_R)<=DOT_COH_RANGE && delta_F <=DOT_COH_AZI/180.0*PI && fabs(point_0_V-point_1_V)<=DOT_COH_V)//
if( (fabs(point_0_R-point_1_R)<=rdp_parameter.point_cohesion_range && delta_F <=rdp_parameter.point_cohesion_azimuth/180.0*PI && fabs(point_0_V-point_1_V)<=rdp_parameter.point_cohesion_velocity)//
&& point_0_A <point_1_A )//
{
point_0_R=static_cast<float>((*data_input)[i].range);
@@ -45,7 +43,7 @@ int Dot_Coh::dot_coh_process(std::vector <PointRecv> *data_input,
point_0_F=static_cast<float>((*data_input)[i].azimuth);
(*data_input)[loop_of_point].use_flag=1;
}
else if( (fabs(point_0_R-point_1_R)<=DOT_COH_RANGE && delta_F <=DOT_COH_AZI/180.0*PI && fabs(point_0_V-point_1_V)<=DOT_COH_V)//
else if( (fabs(point_0_R-point_1_R)<=rdp_parameter.point_cohesion_range && delta_F <=rdp_parameter.point_cohesion_azimuth/180.0*PI && fabs(point_0_V-point_1_V)<=rdp_parameter.point_cohesion_velocity)//
&& point_0_A >= point_1_A)//
{
(*data_input)[i].use_flag=1;
@@ -54,7 +52,7 @@ int Dot_Coh::dot_coh_process(std::vector <PointRecv> *data_input,
else
{
float delta_F = fabs(point_0_F-point_1_F)<2*PI-fabs(point_0_F-point_1_F) ? fabs(point_0_F-point_1_F) :2*PI-fabs(point_0_F-point_1_F);
if( (fabs(point_0_R-point_1_R)<=DOT_COH_RANGE && delta_F <=DOT_COH_AZI/180.0*PI )//
if( (fabs(point_0_R-point_1_R)<=rdp_parameter.point_cohesion_range && delta_F <=rdp_parameter.point_cohesion_azimuth/180.0*PI )//
&& point_0_A <point_1_A )//
{
point_0_R=static_cast<float>((*data_input)[i].range);
@@ -63,7 +61,7 @@ int Dot_Coh::dot_coh_process(std::vector <PointRecv> *data_input,
point_0_F=static_cast<float>((*data_input)[i].azimuth);
(*data_input)[loop_of_point].use_flag=1;
}
else if( (fabs(point_0_R-point_1_R)<=DOT_COH_RANGE && delta_F <=DOT_COH_AZI/180.0*PI )//
else if( (fabs(point_0_R-point_1_R)<=rdp_parameter.point_cohesion_range && delta_F <=rdp_parameter.point_cohesion_azimuth/180.0*PI )//
&& point_0_A >= point_1_A)//
{
(*data_input)[i].use_flag=1;
@@ -80,7 +78,7 @@ int Dot_Coh::dot_coh_process(std::vector <PointRecv> *data_input,
std::vector <PointRecv>::iterator Iter;
for (Iter=data_input->begin(); Iter!=data_input->end();)
{
if((*Iter).use_flag==1 || (*Iter).range <R_MIN || (*Iter).range > R_MAX )
if((*Iter).use_flag==1 || (*Iter).range <rdp_parameter.point_cohesion_range_min || (*Iter).range > rdp_parameter.point_cohesion_range_max )
{
data_input->erase(Iter);
Iter=data_input->begin();
@@ -100,8 +98,7 @@ int Dot_Coh::dot_coh_process(std::vector <PointRecv> *data_input,
}
int Dot_Coh::dot_coh_process_buff( std::vector <PointRecv> *data_input, //输入的点迹
std::vector <PointRecv> *point_recv, //输出点迹
struct RadarPara Work_Parameter //工作参数
std::vector <PointRecv> *point_recv //输出点迹
)
{
@@ -149,10 +146,10 @@ int Dot_Coh::dot_coh_process_buff( std::vector <PointRecv> *data_input,
float point_1_A=static_cast<float>(data_tmp[i].amplitude);
//凝聚条件: 距离、方位接近
if(Work_Parameter.work_mode == 0)
if(rdp_parameter.radar_work_mode == 0)
{
float delta_F = fabs(point_0_F-point_1_F)<2*PI-fabs(point_0_F-point_1_F) ? fabs(point_0_F-point_1_F) :2*PI-fabs(point_0_F-point_1_F);
if( (fabs(point_0_R-point_1_R)<=DOT_COH_RANGE && delta_F <=DOT_COH_AZI/180.0*PI && fabs(point_0_V-point_1_V)<=DOT_COH_V)//
if( (fabs(point_0_R-point_1_R)<=rdp_parameter.point_cohesion_range && delta_F <=rdp_parameter.point_cohesion_azimuth/180.0*PI && fabs(point_0_V-point_1_V)<=rdp_parameter.point_cohesion_velocity)//
&& point_0_A <point_1_A )//
{
point_0_R=static_cast<float>(data_tmp[i].range);
@@ -161,7 +158,7 @@ int Dot_Coh::dot_coh_process_buff( std::vector <PointRecv> *data_input,
point_0_F=static_cast<float>(data_tmp[i].azimuth);
data_tmp[loop_of_point].use_flag=1;
}
else if( (fabs(point_0_R-point_1_R)<=DOT_COH_RANGE && delta_F <=DOT_COH_AZI/180.0*PI && fabs(point_0_V-point_1_V)<=DOT_COH_V)//
else if( (fabs(point_0_R-point_1_R)<=rdp_parameter.point_cohesion_range && delta_F <=rdp_parameter.point_cohesion_azimuth/180.0*PI && fabs(point_0_V-point_1_V)<=rdp_parameter.point_cohesion_velocity)//
&& point_0_A >= point_1_A)//
{
data_tmp[i].use_flag=1;
@@ -170,7 +167,7 @@ int Dot_Coh::dot_coh_process_buff( std::vector <PointRecv> *data_input,
else
{
float delta_F = fabs(point_0_F-point_1_F)<2*PI-fabs(point_0_F-point_1_F) ? fabs(point_0_F-point_1_F) :2*PI-fabs(point_0_F-point_1_F);
if( (fabs(point_0_R-point_1_R)<=DOT_COH_RANGE && delta_F <=DOT_COH_AZI/180.0*PI )//
if( (fabs(point_0_R-point_1_R)<=rdp_parameter.point_cohesion_range && delta_F <=rdp_parameter.point_cohesion_azimuth/180.0*PI )//
&& point_0_A <point_1_A )//
{
point_0_R=static_cast<float>(data_tmp[i].range);
@@ -179,7 +176,7 @@ int Dot_Coh::dot_coh_process_buff( std::vector <PointRecv> *data_input,
point_0_F=static_cast<float>(data_tmp[i].azimuth);
data_tmp[loop_of_point].use_flag=1;
}
else if( (fabs(point_0_R-point_1_R)<=DOT_COH_RANGE && delta_F <=DOT_COH_AZI/180.0*PI )//
else if( (fabs(point_0_R-point_1_R)<=rdp_parameter.point_cohesion_range && delta_F <=rdp_parameter.point_cohesion_azimuth/180.0*PI )//
&& point_0_A >= point_1_A)//
{
data_tmp[i].use_flag=1;
@@ -196,7 +193,7 @@ int Dot_Coh::dot_coh_process_buff( std::vector <PointRecv> *data_input,
std::vector <PointRecv>::iterator Iter;
for (Iter=data_tmp.begin(); Iter!=data_tmp.end();)
{
if((*Iter).use_flag==1 || (*Iter).range <R_MIN || (*Iter).range > R_MAX )
if((*Iter).use_flag==1 || (*Iter).range <rdp_parameter.point_cohesion_range_min || (*Iter).range > rdp_parameter.point_cohesion_range_max )
{
data_tmp.erase(Iter);
Iter=data_tmp.begin();
@@ -224,11 +221,21 @@ int Dot_Coh::dot_coh_process_buff( std::vector <PointRecv> *data_input,
//2.data_input_buff数据输出给point_recv
#ifdef RDP_RECORD_ENABLE
//记录凝聚后点迹:先收集,输出循环结束后一次入队
std::vector<PointRecv> rec_coh;
rec_coh.reserve(data_input_buff.size());
#endif
for (size_t i=0;i<data_input_buff.size();i++)
{
(*point_recv).push_back(data_input_buff[i]);
RDP_RECORD(RdpRecPointCohesionTWS(data_input_buff[i]));
#ifdef RDP_RECORD_ENABLE
rec_coh.push_back(data_input_buff[i]);
#endif
}
#ifdef RDP_RECORD_ENABLE
RDP_RECORD_COHESION_TWS(rec_coh);
#endif
std::vector<PointRecv>().swap(data_input_buff);
+2 -4
View File
@@ -14,14 +14,12 @@ public:
// 点迹凝聚函数
int dot_coh_process( std::vector <PointRecv> *data_input, //输入的点迹
std::vector <PointRecv> *point_recv, //输出点迹
struct RadarPara Work_Parameter //工作参数
std::vector <PointRecv> *point_recv //输出点迹
);
//点迹凝聚函数,缓存一帧,一边输出,一边进行滑窗凝聚
int dot_coh_process_buff( std::vector <PointRecv> *data_input, //输入的点迹
std::vector <PointRecv> *point_recv, //输出点迹
struct RadarPara Work_Parameter //工作参数
std::vector <PointRecv> *point_recv //输出点迹
);
//构造函数
+15 -7
View File
@@ -3,12 +3,10 @@
#include <cstring>
#include <vector>
#include "rdp_record.h"
#include "rdp_record_entity.h"
using namespace std;
int Dot_Coh_TAS::dot_coh_tas_process(std::vector <PointRecv> *data_input, //输入点迹
std::vector <PointRecv> *point_recv_tas //输出点迹
)
std::vector <PointRecv> *point_recv_tas) //输出点迹
{
if (data_input->size() == 0) {
return 1;
@@ -33,7 +31,7 @@ int Dot_Coh_TAS::dot_coh_tas_process(std::vector <PointRecv> *dat
//凝聚条件: 距离、方位接近
float delta_F = fabs(point_0_F-point_1_F)<2*PI-fabs(point_0_F-point_1_F) ? fabs(point_0_F-point_1_F) :2*PI-fabs(point_0_F-point_1_F);
if( (fabs(point_0_R-point_1_R)<=DOT_COH_RANGE && delta_F <=DOT_COH_AZI/180.0*PI && fabs(point_0_V-point_1_V)<=DOT_COH_V)
if( (fabs(point_0_R-point_1_R)<=rdp_parameter.point_cohesion_range && delta_F <=rdp_parameter.point_cohesion_azimuth/180.0*PI && fabs(point_0_V-point_1_V)<=rdp_parameter.point_cohesion_velocity)
&& point_0_A <point_1_A )
{
point_0_R=static_cast<float>((*data_input)[i].range);
@@ -42,7 +40,7 @@ int Dot_Coh_TAS::dot_coh_tas_process(std::vector <PointRecv> *dat
point_0_F=static_cast<float>((*data_input)[i].azimuth);
(*data_input)[loop_of_point].use_flag=1;
}
else if( (fabs(point_0_R-point_1_R)<=DOT_COH_RANGE && delta_F <=DOT_COH_AZI/180.0*PI && fabs(point_0_V-point_1_V)<=DOT_COH_V)
else if( (fabs(point_0_R-point_1_R)<=rdp_parameter.point_cohesion_range && delta_F <=rdp_parameter.point_cohesion_azimuth/180.0*PI && fabs(point_0_V-point_1_V)<=rdp_parameter.point_cohesion_velocity)
&& point_0_A >= point_1_A)
{
(*data_input)[i].use_flag=1;
@@ -56,7 +54,7 @@ int Dot_Coh_TAS::dot_coh_tas_process(std::vector <PointRecv> *dat
std::vector <PointRecv>::iterator Iter;
for (Iter=data_input->begin(); Iter!=data_input->end();)
{
if((*Iter).use_flag==1 || (*Iter).range <R_MIN || (*Iter).range > R_MAX )
if((*Iter).use_flag==1 || (*Iter).range <rdp_parameter.point_cohesion_range_min || (*Iter).range > rdp_parameter.point_cohesion_range_max )
{
data_input->erase(Iter);
Iter=data_input->begin();
@@ -67,11 +65,21 @@ int Dot_Coh_TAS::dot_coh_tas_process(std::vector <PointRecv> *dat
}
}
#ifdef RDP_RECORD_ENABLE
//记录凝聚后点迹:先收集,输出循环结束后一次入队
std::vector<PointRecv> rec_coh;
rec_coh.reserve(data_input->size());
#endif
for (size_t i=0;i<data_input->size();i++)
{
(*point_recv_tas).push_back((*data_input)[i]);
RDP_RECORD(RdpRecPointCohesionTAS((*data_input)[i]));
#ifdef RDP_RECORD_ENABLE
rec_coh.push_back((*data_input)[i]);
#endif
}
#ifdef RDP_RECORD_ENABLE
RDP_RECORD_COHESION_TAS(rec_coh);
#endif
return 1;
}
+53 -53
View File
@@ -26,13 +26,13 @@ void kalman:: kalman_filter_init_2dots(double Z0[2], double Z1[2], double T,doub
double rho,theta;
coor_trans Coor_trans;
Coor_trans.cart2polar(Z1[0],Z1[1],&rho,&theta);
double lambda_theta=exp(-SIGMA_A*SIGMA_A/2);
double lambda_theta1=exp(-2*SIGMA_A*SIGMA_A);
double lambda_theta=exp(-rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth/2);
double lambda_theta1=exp(-2*rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth);
double R[2][2];
R[0][0]=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1+lambda_theta1*cos(2*theta));
R[1][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1-lambda_theta1*cos(2*theta));
R[0][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*lambda_theta1*sin(2*theta);
R[0][0]=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1+lambda_theta1*cos(2*theta));
R[1][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1-lambda_theta1*cos(2*theta));
R[0][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*lambda_theta1*sin(2*theta);
R[1][0]=R[0][1];
P[0][0]=R[0][0];
@@ -92,13 +92,13 @@ double kalman::d_cal_track_init(double Z[2],double X[4],double P[4][4],double T)
double rho,theta;
coor_trans Coor_trans;
Coor_trans.cart2polar(Z[0],Z[1],&rho,&theta);
double lambda_theta=exp(-SIGMA_A*SIGMA_A/2);
double lambda_theta1=exp(-2*SIGMA_A*SIGMA_A);
double lambda_theta=exp(-rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth/2);
double lambda_theta1=exp(-2*rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth);
Matrix2d R;
R(0,0)=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1+lambda_theta1*cos(2*theta));
R(1,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1-lambda_theta1*cos(2*theta));
R(0,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*lambda_theta1*sin(2*theta);
R(0,0)=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1+lambda_theta1*cos(2*theta));
R(1,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1-lambda_theta1*cos(2*theta));
R(0,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*lambda_theta1*sin(2*theta);
R(1,0)=R(0,1);
Matrix4d P_present;
@@ -145,23 +145,23 @@ void kalman::kalman_filter_init_3dots(double Z0[2], double Z1[2],double Z2[2], d
double rho,theta;
coor_trans Coor_trans;
Coor_trans.cart2polar(Z0[0],Z0[1],&rho,&theta);
double lambda_theta=exp(-SIGMA_A*SIGMA_A/2);
double lambda_theta1=exp(-2*SIGMA_A*SIGMA_A);
R0[0][0]=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1+lambda_theta1*cos(2*theta));
R0[1][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1-lambda_theta1*cos(2*theta));
R0[0][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*lambda_theta1*sin(2*theta);
double lambda_theta=exp(-rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth/2);
double lambda_theta1=exp(-2*rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth);
R0[0][0]=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1+lambda_theta1*cos(2*theta));
R0[1][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1-lambda_theta1*cos(2*theta));
R0[0][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*lambda_theta1*sin(2*theta);
R0[1][0]=R0[0][1];
Coor_trans.cart2polar(Z1[0],Z1[1],&rho,&theta);
R1[0][0]=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1+lambda_theta1*cos(2*theta));
R1[1][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1-lambda_theta1*cos(2*theta));
R1[0][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*lambda_theta1*sin(2*theta);
R1[0][0]=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1+lambda_theta1*cos(2*theta));
R1[1][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1-lambda_theta1*cos(2*theta));
R1[0][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*lambda_theta1*sin(2*theta);
R1[1][0]=R1[0][1];
Coor_trans.cart2polar(Z2[0],Z2[1],&rho,&theta);
R2[0][0]=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1+lambda_theta1*cos(2*theta));
R2[1][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1-lambda_theta1*cos(2*theta));
R2[0][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*lambda_theta1*sin(2*theta);
R2[0][0]=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1+lambda_theta1*cos(2*theta));
R2[1][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1-lambda_theta1*cos(2*theta));
R2[0][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*lambda_theta1*sin(2*theta);
R2[1][0]=R2[0][1];
double P11[3][3]={{R2[0][0], R2[0][0]/T2, (R2[0][0]/T2)/((T2+T1)/2)} ,
@@ -218,13 +218,13 @@ double kalman::d_cal(double Z[2],double X[6],double P[6][6])
double rho,theta;
coor_trans Coor_trans;
Coor_trans.cart2polar(Z[0],Z[1],&rho,&theta);
double lambda_theta=exp(-SIGMA_A*SIGMA_A/2);
double lambda_theta1=exp(-2*SIGMA_A*SIGMA_A);
double lambda_theta=exp(-rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth/2);
double lambda_theta1=exp(-2*rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth);
Matrix2d R;
R(0,0)=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1+lambda_theta1*cos(2*theta));
R(1,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1-lambda_theta1*cos(2*theta));
R(0,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*lambda_theta1*sin(2*theta);
R(0,0)=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1+lambda_theta1*cos(2*theta));
R(1,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1-lambda_theta1*cos(2*theta));
R(0,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*lambda_theta1*sin(2*theta);
R(1,0)=R(0,1);
//S
@@ -284,9 +284,9 @@ double kalman::d_cal_EKF(double F[6][6], double Q[6][6] ,double Z[3],double X[6]
H(2,3) = -x*(vy*x-vx*y)/((x*x+y*y)*sqrt(x*x+y*y)); H(2,4)=-y/sqrt(x*x+y*y); H(2,5)=0;
Matrix3d R;
R(0,0)=SIGMA_R*SIGMA_R;
R(1,1)=SIGMA_A*SIGMA_A;
R(2,2)=SIGMA_V*SIGMA_V;
R(0,0)=rdp_parameter.sigma_range*rdp_parameter.sigma_range;
R(1,1)=rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth;
R(2,2)=rdp_parameter.sigma_velocity*rdp_parameter.sigma_velocity;
R(0,1)=0;
R(1,0)=0;
R(2,0)=0;
@@ -362,15 +362,15 @@ double kalman:: d_cal_with_doppler(double F[6][6], double Q[6][6] , double Z[2],
double rho,theta;
coor_trans Coor_trans;
Coor_trans.cart2polar(Z[0],Z[1],&rho,&theta);
double lambda_theta=exp(-SIGMA_A*SIGMA_A/2);
double lambda_theta1=exp(-2*SIGMA_A*SIGMA_A);
double lambda_theta=exp(-rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth/2);
double lambda_theta1=exp(-2*rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth);
Matrix3d R;
R(0,0)=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1+lambda_theta1*cos(2*theta));
R(1,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1-lambda_theta1*cos(2*theta));
R(0,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*lambda_theta1*sin(2*theta);
R(0,0)=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1+lambda_theta1*cos(2*theta));
R(1,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1-lambda_theta1*cos(2*theta));
R(0,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*lambda_theta1*sin(2*theta);
R(1,0)=R(0,1);
R(2,2)=SIGMA_V*SIGMA_V;
R(2,2)=rdp_parameter.sigma_velocity*rdp_parameter.sigma_velocity;
R(2,0)=0;
R(2,1)=0;
R(0,2)=0;
@@ -477,9 +477,9 @@ void kalman::kalman_filter_EKF(double F[6][6], double Q[6][6], double X_cur[6],d
H(2,3) = -x*(vy*x-vx*y)/((x*x+y*y)*sqrt(x*x+y*y)); H(2,4)=-y/sqrt(x*x+y*y); H(2,5)=0;
Matrix3d R;
R(0,0)=SIGMA_R*SIGMA_R;
R(1,1)=SIGMA_A*SIGMA_A;
R(2,2)=SIGMA_V*SIGMA_V;
R(0,0)=rdp_parameter.sigma_range*rdp_parameter.sigma_range;
R(1,1)=rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth;
R(2,2)=rdp_parameter.sigma_velocity*rdp_parameter.sigma_velocity;
R(0,1)=0;
R(1,0)=0;
R(2,0)=0;
@@ -570,13 +570,13 @@ void kalman::kalman_filter(double F[6][6], double Q[6][6],
double rho,theta;
coor_trans Coor_trans;
Coor_trans.cart2polar(Z[0],Z[1],&rho,&theta);
double lambda_theta=exp(-SIGMA_A*SIGMA_A/2);
double lambda_theta1=exp(-2*SIGMA_A*SIGMA_A);
double lambda_theta=exp(-rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth/2);
double lambda_theta1=exp(-2*rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth);
Matrix2d R;
R(0,0)=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1+lambda_theta1*cos(2*theta));
R(1,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1-lambda_theta1*cos(2*theta));
R(0,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*lambda_theta1*sin(2*theta);
R(0,0)=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1+lambda_theta1*cos(2*theta));
R(1,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1-lambda_theta1*cos(2*theta));
R(0,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*lambda_theta1*sin(2*theta);
R(1,0)=R(0,1);
//S
@@ -663,9 +663,9 @@ double kalman::d_cal_track_init_EKF(double Z[3],double X[4],double P[4][4],doubl
H(2,2) = -x*(vy*x-vx*y)/((x*x+y*y)*sqrt(x*x+y*y)); H(2,3)=-y/sqrt(x*x+y*y);
Matrix3d R;
R(0,0)=SIGMA_R*SIGMA_R;
R(1,1)=SIGMA_A*SIGMA_A;
R(2,2)=SIGMA_V*SIGMA_V;
R(0,0)=rdp_parameter.sigma_range*rdp_parameter.sigma_range;
R(1,1)=rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth;
R(2,2)=rdp_parameter.sigma_velocity*rdp_parameter.sigma_velocity;
R(0,1)=0;
R(1,0)=0;
R(2,0)=0;
@@ -744,15 +744,15 @@ double kalman::d_cal_track_init_with_doppler(double Z[2],double X[4],double P[4]
double rho,theta;
coor_trans Coor_trans;
Coor_trans.cart2polar(Z[0],Z[1],&rho,&theta);
double lambda_theta=exp(-SIGMA_A*SIGMA_A/2);
double lambda_theta1=exp(-2*SIGMA_A*SIGMA_A);
double lambda_theta=exp(-rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth/2);
double lambda_theta1=exp(-2*rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth);
Matrix3d R;
R(0,0)=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1+lambda_theta1*cos(2*theta));
R(1,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1-lambda_theta1*cos(2*theta));
R(0,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*lambda_theta1*sin(2*theta);
R(0,0)=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1+lambda_theta1*cos(2*theta));
R(1,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1-lambda_theta1*cos(2*theta));
R(0,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*lambda_theta1*sin(2*theta);
R(1,0)=R(0,1);
R(2,2)=SIGMA_V*SIGMA_V;
R(2,2)=rdp_parameter.sigma_velocity*rdp_parameter.sigma_velocity;
R(2,0)=0;
R(2,1)=0;
R(0,2)=0;
@@ -779,7 +779,7 @@ double kalman::d_cal_track_init_with_doppler(double Z[2],double X[4],double P[4]
double kalman::Bind_speed(double prt,double freq_ind)
{
double freq=FREQ0+freq_ind*0.02;
double freq=rdp_parameter.freq_0_GHz+freq_ind*0.02;
if (prt <= 0.0 || freq <= 0.0)
return 1.0; // 避免除零;正常流程 PRI 不允许为 0
+59 -117
View File
@@ -4,18 +4,11 @@
/**********************************雷达参数*************************************************/
#define PI 3.1415926f
//频点(GHz)
#define FREQ0 9.2
/**********************************版本信息*************************************************/
#define MAJOR_VERSION 1
#define MINOR_VERSION 5
#define PATCH_VERSION 6
#ifdef PHASE_SCANNING
#define VERSION_INFO "PS"
#else
#define VERSION_INFO "MS"
#endif
#define PATCH_VERSION 9
#define VERSION_INFO "GENERIC"
#define STRINGIFY_IMPL(x) #x
#define STRINGIFY(x) STRINGIFY_IMPL(x)
@@ -26,13 +19,15 @@
STRINGIFY(PATCH_VERSION) "." \
VERSION_INFO
/**********************************日志输出开关**********************************************/
// 日志文件输出功能开关:
// 1) 定义(默认)RDP_LOG_ENABLE:启用日志文件输出 —— 调用 Data_Process_Factory::GetB()
// 时自动将 std::cout 重定向到日志文件,日志文件存放于 RDP_LOG_DIR 目录,
// 文件名按当前日期+时间自动命名(如 rdp_20240826_153000.log);
// 2) 注释掉 RDP_LOG_ENABLE:关闭 —— 不做重定向,日志仍输出到标准输出(控制台)。
// #define RDP_LOG_ENABLE
/**********************************日志输出*************************************************/
// 日志基于内置 spdlog v1.17.0(third_party/spdlog,来源见其 UPSTREAM.md):
// 1) 每次调用 Data_Process_Factory::GetB() 创建实例时,rdp_log::Init() 同时输出到
// 标准输出与日志文件(RDP_LOG_DIR 目录),日志文件名按当前日期+时间自动命名
// (如 rdp_20240826_153000.log,每次初始化一个文件);Destroy() 时关闭日志文件;
// 2) 原 RDP_LOG_ENABLE 布尔开关已移除:日志级别改由 spdlog 的 SPDLOG_ACTIVE_LEVEL
// 在编译期控制,由 CMake 选项 RDP_LOG_LEVEL(AUTO/TRACE/DEBUG/INFO/WARN/ERROR/
// CRITICAL/OFF)映射;AUTO 时 Debug 构建 = TRACE、其余构建 = INFO,OFF 表示
// 编译期关闭全部 RDP_LOG_* 日志。低于该级别的日志实参不求值。详见 README 10.4。
//日志文件输出目录(相对当前工作目录的路径,目录不存在时自动创建)
#define RDP_LOG_DIR ".\\rdp_logs\\"
@@ -46,111 +41,58 @@
//记录数据库存放目录(相对宿主进程当前工作目录,不存在时自动创建)
#define RDP_RECORD_DB_DIR ".\\rdp_records\\"
//#define MECHANICAL_SCANNING
//#define PHASE_SCANNING
#ifdef MECHANICAL_SCANNING
/******************************数据处理参数**************************************/
// #define DATA_RATE_TAS 0.0625
#define SIGMA_R 10.0 //测量误差
#define SIGMA_A 0.02
#define SIGMA_E 0.2
#define SIGMA_V 2.0
#define DOT_COH_RANGE 80 //点迹凝聚
#define DOT_COH_V 2
#define DOT_COH_AZI 6
#define MAX_BEAM_NUM 100 //最大TWS波位数
#define MAX_TRACK_NUM 500
#define MAX_TRACK_INDEX 500 //最大航迹批号
#define ASSOCIATE_THRESHOLD_MIN 1
#define ASSOCIATE_THRESHOLD_MID 3 //关联波门
#define ASSOCIATE_THRESHOLD_MAX 10
#define R_MIN 100
#define R_MAX 100000
#define TRACK_START_THRESHOLD 8 //起航波门 3 越大越容易起批
#define ALPHA_START 60 //起航夹角 120 越大越容易起批
#define MAX_EXTRAPOLATE_NUM 3 //最大外推点数
#define MAX_EXTRAPOLATE_NUM_TAS 48 //TAS最大外推点数
#define TRACK_DIE_ROUND 6 // 航迹消亡圈数
#define TRACK_DIE_ROUND_PRIMARY 10 // 重点航迹消亡圈数
#define TRACK_DIE_ROUND_TAS 96 //TAS航迹消亡圈数
#define ASSO_THORD 4
#define H_F_WIN_LEN 3
#define TAS_QUEUE_LENGTH 1 //Tas调用的cpi数
#define MAX_TAS_NUM 1
#define UNCONF_TARGET 0
#define DIRECT_TRACKING_QUEUE_LENGTH 10
#pragma message("MECHANICAL_SCANNING")
//数据记录性能参数(V1.5.8 优化:批量事务 + 异步写线程 + WAL)
//说明:以下宏只被 rdp_record.cpp 使用;用 #ifndef 保护,便于用编译选项临时覆盖做测试。
//队列字节上限:处理线程入队总量超过该值时整批丢弃(不阻塞处理线程)
#ifndef RDP_RECORD_QUEUE_CAP_BYTES
#define RDP_RECORD_QUEUE_CAP_BYTES (64u * 1024u * 1024u)
#endif
//队列批数上限(防止小批过多导致 deque 开销/延迟)
#ifndef RDP_RECORD_QUEUE_MAX_BATCHES
#define RDP_RECORD_QUEUE_MAX_BATCHES 4096u
#endif
//写线程每次事务提交的目标行数(累积到该行数或等待超时即提交)
#ifndef RDP_RECORD_WRITE_BATCH_ROWS
#define RDP_RECORD_WRITE_BATCH_ROWS 4096u
#endif
//写线程为凑批而等待的最长时间(毫秒)
#ifndef RDP_RECORD_WRITE_MAX_WAIT_MS
#define RDP_RECORD_WRITE_MAX_WAIT_MS 50u
#endif
//关联信息单次入队分块上限(⑥ 单圈行数可能很大,分块限制处理线程内存峰值;
//取值应大于常见单圈候选数,避免同一圈内多次重新分配;131072 行 Asso_Info 约 11 MB)
#ifndef RDP_RECORD_ASSO_CHUNK_ROWS
#define RDP_RECORD_ASSO_CHUNK_ROWS 131072u
#endif
//SQLite 页缓存大小(KB,负数表示 KB;默认 8 MB)
#ifndef RDP_RECORD_SQLITE_CACHE_KB
#define RDP_RECORD_SQLITE_CACHE_KB 8192
#endif
//SQLite 忙等待超时(毫秒)
#ifndef RDP_RECORD_SQLITE_BUSY_TIMEOUT_MS
#define RDP_RECORD_SQLITE_BUSY_TIMEOUT_MS 2000
#endif
//是否尝试开启 WAL(1=尝试;失败/不支持时自动回退 DELETE 日志模式)
#ifndef RDP_RECORD_SQLITE_WAL
#define RDP_RECORD_SQLITE_WAL 1
#endif
//连续写入错误达到该次数后全局熔断(停止记录,避免持续消耗写线程)
#ifndef RDP_RECORD_MAX_CONSECUTIVE_ERRORS
#define RDP_RECORD_MAX_CONSECUTIVE_ERRORS 10
#endif
//队列丢弃日志的限频间隔(每丢弃多少行提示一次)
#ifndef RDP_RECORD_DROP_LOG_INTERVAL
#define RDP_RECORD_DROP_LOG_INTERVAL 1024u
#endif
#ifdef PHASE_SCANNING
/******************************数据处理参数**************************************/
// #define DATA_RATE_TAS 0.3
#define SIGMA_R 10.0 //测量误差
#define SIGMA_A 0.02
#define SIGMA_E 0.2
#define SIGMA_V 2.0
#define DOT_COH_RANGE 80 //点迹凝聚
#define DOT_COH_V 2
#define DOT_COH_AZI 6
#define MAX_BEAM_NUM 100 //最大TWS波位数
/******************************编译期容量常量***********************************************/
// 说明:下列常量是「接口/内存布局」需要的编译期容量,不是雷达工作参数,
// 雷达工作参数统一由 TOML 配置文件经 parameters_reload() 载入 struct.h::RDP_parameter。
//输出航迹数组的容量(宿主接口固定维度,见 data_process_class_dll.h)
#define MAX_TRACK_NUM 500
#define MAX_TRACK_INDEX 500 //最大航迹批号
#define ASSOCIATE_THRESHOLD_MIN 1
#define ASSOCIATE_THRESHOLD_MID 3 //关联波门
#define ASSOCIATE_THRESHOLD_MAX 10
#define R_MIN 100
#define R_MAX 100000
//#define V_MAX 500 //最大速度
//#define V_MIN 1 //速度最小
#define TRACK_START_THRESHOLD 8 //起航波门 3 越大越容易起批
#define ALPHA_START 60 //起航夹角 120 越大越容易起批
#define MAX_EXTRAPOLATE_NUM 3 //最大外推点数
#define MAX_EXTRAPOLATE_NUM_TAS 5 //TAS最大外推点数
#define TRACK_DIE_ROUND 6 // 航迹消亡圈数
#define TRACK_DIE_ROUND_PRIMARY 10 // 重点航迹消亡圈数
#define TRACK_DIE_ROUND_TAS 10 //TAS航迹消亡圈数
#define ASSO_THORD 4
#define H_F_WIN_LEN 3
#define TAS_QUEUE_LENGTH 4 //Tas调用的cpi数
#define MAX_TAS_NUM 4
#define UNCONF_TARGET 0
#define DIRECT_TRACKING_QUEUE_LENGTH 10
#pragma message("PHASE_SCANNING")
#endif
//TAS 跟踪队列容量上限(运行期配置项 tas_track_num_max 不得超过该值)
#define RDP_TAS_QUEUE_CAPACITY 30
#define Round(x) (((x) > 0) \
? (((int)((x) + 0.5) > (int)(x)) ? ((int)(x) + 1) : ((int)(x))) \
+133 -91
View File
@@ -1,32 +1,30 @@
#include "rdp_log.h"
#include "parameters.h"
#include <fstream>
#include <sstream>
#include <cstdio>
#include <memory>
#include <string>
#include <vector>
#include <time.h>
#include <direct.h>
#include <io.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_sinks.h>
namespace
{
//日志模块内部状态:日志文件流 + 重定向前 std::cout 的原始缓冲区
//日志行格式:与旧版 [时间戳][文件:行号] 风格一致,另加 spdlog 级别
const char *const kPattern = "[%Y-%m-%d %H:%M:%S.%e] [%l] [%s:%#] %v";
//日志模块内部状态:默认 logger(标准输出 + 日志文件)与当前日志文件路径
struct RdpLogState
{
std::ofstream file; //日志文件输出流
std::streambuf *original_buf; //重定向前 std::cout 的原始缓冲区
bool enabled; //是否已成功重定向到日志文件
std::shared_ptr<spdlog::logger> logger; //默认 logger(含标准输出 sink,成功时另含文件 sink)
std::string log_path; //当前日志文件全路径(降级为仅标准输出时为空)
bool initialized; //是否已初始化(含文件创建失败时的降级初始化)
RdpLogState() : original_buf(0), enabled(false) {}
~RdpLogState()
{
//进程退出兜底:若宿主进程未调用 Data_Process_Factory::Destroy(),
//此处恢复 std::cout,避免其仍指向即将析构的日志文件流缓冲区。
if (enabled && original_buf)
{
std::cout.rdbuf(original_buf);
enabled = false;
}
}
RdpLogState() : initialized(false) {}
};
RdpLogState &State()
@@ -35,36 +33,78 @@ namespace
return s;
}
//当前本地时间字符串,格式 "YYYY-MM-DD HH:MM:SS"
std::string CurrentTime()
//创建日志目录(目录已存在时 _mkdir 返回 EEXIST,忽略;逻辑与原 rdp_log/rdp_record 一致)
void EnsureDir(const std::string &dir_with_separator)
{
std::string dir = dir_with_separator;
while (dir.size() > 0 && (dir[dir.size() - 1] == '\\' || dir[dir.size() - 1] == '/'))
{
dir.erase(dir.size() - 1);
}
if (dir.size() > 0)
{
_mkdir(dir.c_str());
}
}
//当前本地时间字符串,格式 "YYYYMMDD_HHMMSS"
bool TimeStamp(char *buf, size_t size)
{
char buf[32] = {0};
time_t now = time(NULL);
struct tm local;
if (localtime_s(&local, &now) != 0)
{
return std::string();
return false;
}
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &local);
return std::string(buf);
return strftime(buf, size, "%Y%m%d_%H%M%S", &local) != 0;
}
//提取纯文件名(去掉路径前缀),日志前缀中只显示 文件名:行号
const char *FileName(const char *path)
//生成日志文件路径:RDP_LOG_DIR + rdp_YYYYMMDD_HHMMSS.log;
//同一秒内文件已存在(Destroy→GetB 重连、同目录多进程启动)时追加 _1~_999 后缀,不复用同一文件
bool MakeLogPath(std::string *path)
{
if (path == 0)
char stamp[32] = {0};
if (!TimeStamp(stamp, sizeof(stamp)))
{
return "";
return false;
}
const char *base = path;
for (const char *p = path; *p != '\0'; ++p)
const std::string base = std::string(RDP_LOG_DIR) + "rdp_" + stamp;
for (int suffix = 0; suffix < 1000; ++suffix)
{
if (*p == '\\' || *p == '/')
std::string candidate = base;
if (suffix > 0)
{
base = p + 1;
candidate += "_" + std::to_string(suffix);
}
candidate += ".log";
if (_access(candidate.c_str(), 0) != 0)
{
*path = candidate;
return true;
}
}
return base;
return false;
}
//初始化 logger:统一日志格式,运行期级别放开到 trace(实际输出级别由编译期
//SPDLOG_ACTIVE_LEVEL 过滤),所有级别逐条 flush(日志量小,且调试场景要求异常退出也尽量不丢日志)
void ConfigureLogger(const std::shared_ptr<spdlog::logger> &logger)
{
logger->set_pattern(kPattern);
logger->set_level(spdlog::level::trace);
logger->flush_on(spdlog::level::trace);
spdlog::set_default_logger(logger);
}
//仅标准输出 sink 的降级 logger(日志文件创建失败时使用)
std::shared_ptr<spdlog::logger> MakeStdoutLogger()
{
std::vector<spdlog::sink_ptr> sinks;
sinks.push_back(std::make_shared<spdlog::sinks::stdout_sink_mt>());
std::shared_ptr<spdlog::logger> logger =
std::make_shared<spdlog::logger>("rdp", sinks.begin(), sinks.end());
ConfigureLogger(logger);
return logger;
}
}
@@ -73,82 +113,84 @@ namespace rdp_log
bool Init()
{
RdpLogState &st = State();
if (st.enabled)
if (st.initialized)
{
return true; //已经重定向,避免重复初始化
return true; //已经初始化,避免重复创建日志文件
}
#ifdef RDP_LOG_ENABLE
//创建日志目录(目录已存在时 _mkdir 返回 EEXIST,忽略)
std::string log_dir = RDP_LOG_DIR;
while (log_dir.size() > 0 &&
(log_dir[log_dir.size() - 1] == '\\' || log_dir[log_dir.size() - 1] == '/'))
{
log_dir.erase(log_dir.size() - 1);
}
if (log_dir.size() > 0)
{
_mkdir(log_dir.c_str());
}
//按当前日期+时间生成日志文件名:rdp_YYYYMMDD_HHMMSS.log
char time_buf[32] = {0};
time_t now = time(NULL);
struct tm local;
if (localtime_s(&local, &now) != 0)
{
#if SPDLOG_ACTIVE_LEVEL > SPDLOG_LEVEL_CRITICAL
//编译期关闭日志(RDP_LOG_LEVEL=OFF):不初始化 logger、不创建日志文件
return false;
}
strftime(time_buf, sizeof(time_buf), "%Y%m%d_%H%M%S", &local);
std::string log_path = RDP_LOG_DIR;
log_path += "rdp_";
log_path += time_buf;
log_path += ".log";
st.file.open(log_path.c_str(), std::ios::out | std::ios::trunc);
if (!st.file.is_open())
{
//日志文件打开失败(目录不可写等):保持控制台输出,不做重定向
std::cout << "[rdp_log] cannot open log file: " << log_path << std::endl;
return false;
}
//用 rdbuf() 将 std::cout 的流缓冲区替换为日志文件输出流
st.original_buf = std::cout.rdbuf();
std::cout.rdbuf(st.file.rdbuf());
st.enabled = true;
//重定向成功后写入起始标记(该行已写入日志文件)
std::cout << prefix(__FILE__, __LINE__)
<< "rdp_log enabled, log file: " << log_path << std::endl;
return true;
#else
//日志开关关闭:不重定向,日志仍输出到标准输出
try
{
EnsureDir(RDP_LOG_DIR);
std::string log_path;
if (!MakeLogPath(&log_path))
{
throw spdlog::spdlog_ex("no usable log file name in RDP_LOG_DIR");
}
std::vector<spdlog::sink_ptr> sinks;
sinks.push_back(std::make_shared<spdlog::sinks::stdout_sink_mt>());
//truncate=false:日志文件名已保证不存在(极端并发下万一被抢占则追加而非覆盖已有内容)
sinks.push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_path, false));
std::shared_ptr<spdlog::logger> logger =
std::make_shared<spdlog::logger>("rdp", sinks.begin(), sinks.end());
ConfigureLogger(logger);
//spdlog 内部错误(如日志文件写失败)打到标准错误,不影响雷达数据处理
spdlog::set_error_handler([](const std::string &msg) {
std::fprintf(stderr, "[rdp_log] spdlog error: %s\n", msg.c_str());
});
st.logger = logger;
st.log_path = log_path;
st.initialized = true;
RDP_LOG_INFO("rdp_log enabled, log file: {}", log_path);
return true;
}
catch (const std::exception &e)
{
//日志文件创建失败(目录不可写等):降级为仅标准输出,不中断雷达数据处理
try
{
st.logger = MakeStdoutLogger();
st.log_path.clear();
st.initialized = true;
std::fprintf(stderr, "[rdp_log] cannot create log file, fallback to stdout only: %s\n",
e.what());
}
catch (...)
{
std::fprintf(stderr, "[rdp_log] logger init failed\n");
}
return false;
}
#endif
}
void Shutdown()
{
RdpLogState &st = State();
if (!st.enabled)
if (!st.initialized || !st.logger)
{
return;
}
//写入结束标记后刷新、恢复 std::cout 并关闭日志文件
std::cout << prefix(__FILE__, __LINE__) << "rdp_log shutdown" << std::endl;
st.file.flush();
std::cout.rdbuf(st.original_buf);
st.enabled = false;
st.file.close();
RDP_LOG_INFO("rdp_log shutdown");
//flush 并释放默认 logger(随之关闭日志文件)
spdlog::shutdown();
st.logger.reset();
st.log_path.clear();
st.initialized = false;
}
std::string prefix(const char *file, int line)
std::string log_file_path()
{
std::ostringstream oss;
oss << "[" << CurrentTime() << "][" << FileName(file) << ":" << line << "] ";
return oss.str();
return State().log_path;
}
}
+35 -15
View File
@@ -2,26 +2,46 @@
#define RDP_LOG_H
#pragma once
/*************************************日志输出模块*******************************************/
// 功能:
// 1) rdp_log::Init():按 parameters.h 中 RDP_LOG_ENABLE 宏开关决定是否将 std::cout
// 重定向到日志文件(RDP_LOG_DIR 目录,文件名按当前日期+时间自动生成)。
// 开关关闭(注释掉 RDP_LOG_ENABLE)时不做重定向,日志仍输出到标准输出。
// 2) rdp_log::Shutdown():恢复 std::cout 原始缓冲区(回到控制台)并关闭日志文件。
// 3) RDP_LOG 宏:日志输出宏,在每条日志前自动附加 [时间戳][代码文件:行号] 信息,
// 随后输出到 std::cout(若开关开启则随 cout 一并写入日志文件)。
// 用法示例:
// RDP_LOG << "目标批号:" << track_index << std::endl;
#include <iostream>
// 基于 spdlog v1.17.0(third_party/spdlog,来源/版本见该目录 UPSTREAM.md,以静态库目标
// spdlog 链接,见 CMakeLists.txt):
// 1) rdp_log::Init():创建 RDP_LOG_DIR 下按「日期+时间」命名的日志文件(每次初始化一个
// 文件;同一秒内文件已存在时追加 _1~_999 后缀),默认 logger 同时输出到 标准输出 与
// 日志文件 两个 sink。由 Data_Process_Factory::GetB() 调用;
// 2) rdp_log::Shutdown():打印结束标记、flush 并释放 logger(关闭日志文件),由
// Data_Process_Factory::Destroy() 调用;
// 3) 日志宏 RDP_LOG_TRACE / RDP_LOG_DEBUG / RDP_LOG_INFO / RDP_LOG_WARN /
// RDP_LOG_ERROR / RDP_LOG_CRITICAL:级别与 spdlog 一致(RDP_LOG_ERR 为
// RDP_LOG_ERROR 的别名),支持 fmt 风格的格式化,用法示例:
// RDP_LOG_INFO("目标批号:{},时间:{}", track_index, cpi_time);
// 低于 SPDLOG_ACTIVE_LEVEL 的日志调用在编译期被剔除(实参不求值),该级别由 CMake
// 选项 RDP_LOG_LEVEL 控制(见 CMakeLists.txt 与 README 10.4),即原 RDP_LOG_ENABLE
// 开关改由 SPDLOG_ACTIVE_LEVEL 在编译期控制日志级别(OFF = 编译期关闭全部日志)。
//
// 日志文件路径/名称与旧实现保持一致:RDP_LOG_DIR(.\rdp_logs\)+ rdp_YYYYMMDD_HHMMSS.log。
// 日志行格式:"[YYYY-MM-DD HH:MM:SS.mmm] [级别] [文件名:行号] 内容",级别为 spdlog 默认
// 名称 trace/debug/info/warning/error/critical。
#include <string>
// 第三方头文件告警隔离:spdlog 按 third_party 依赖处理(见 README 4.5)
#pragma warning(push, 0)
#include <spdlog/spdlog.h>
#pragma warning(pop)
namespace rdp_log
{
bool Init(); //初始化日志输出(由 Data_Process_Factory::GetB() 调用)
void Shutdown(); //恢复 std::cout 并关闭日志文件(由 Data_Process_Factory::Destroy() 调用)
std::string prefix(const char *file, int line); //生成 "[时间戳][文件名:行号] " 前缀字符串
bool Init(); //初始化日志输出(标准输出 + 日志文件;由 GetB() 调用)
void Shutdown(); //flush、释放 logger 并关闭日志文件(由 Destroy() 调用)
std::string log_file_path(); //当前日志文件全路径(未初始化或文件创建失败时为空串)
}
//日志输出宏:自动附加 时间戳、代码文件、代码行号 后输出到标准输出(std::cout)
#define RDP_LOG std::cout << rdp_log::prefix(__FILE__, __LINE__)
//日志级别宏(spdlog 编译期级别过滤:低于 SPDLOG_ACTIVE_LEVEL 的调用被剔除、实参不求值)
#define RDP_LOG_TRACE(...) SPDLOG_TRACE(__VA_ARGS__)
#define RDP_LOG_DEBUG(...) SPDLOG_DEBUG(__VA_ARGS__)
#define RDP_LOG_INFO(...) SPDLOG_INFO(__VA_ARGS__)
#define RDP_LOG_WARN(...) SPDLOG_WARN(__VA_ARGS__)
#define RDP_LOG_ERROR(...) SPDLOG_ERROR(__VA_ARGS__)
#define RDP_LOG_CRITICAL(...) SPDLOG_CRITICAL(__VA_ARGS__)
//兼容别名:需求中的级别名 err 对应 spdlog::level::err,宏名同时提供 RDP_LOG_ERR
#define RDP_LOG_ERR(...) RDP_LOG_ERROR(__VA_ARGS__)
#endif // RDP_LOG_H
@@ -0,0 +1,617 @@
#include "rdp_parameter_loader.h"
#include "parameters.h"
#include <cfloat>
#include <cmath>
#include <cstdint>
#include <limits>
#include <string>
#include <string_view>
#include <vector>
// toml++ 为纯头文件第三方库(third_party/toml/toml.hpp,v3.4.0)。
// 第三方头文件不参与本工程的告警治理:包含期间临时屏蔽告警(/W3 下仍要求 0 告警)。
#if defined(_MSC_VER)
#pragma warning(push, 0)
#endif
#include <toml.hpp>
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
/************************************************************************************************
* 实现说明
*
* 配置文件结构与 RDP_parameter 一一对应:
* [radar_params] 雷达工作参数
* [cohesion] 点迹凝聚参数
* [track_associate] 航迹关联参数
* [track_init] 航迹起始参数
* [track_die] 航迹消亡参数
* [track_quality] 航迹质量管理参数
* [track_index] 航迹批号管理
* [beam_control] 波束调度参数
* [singer_model] singer 模型机动参数
* [track_init_prohibite_area] 航迹起始屏蔽区(数组)
* [tas_prohibite_area] 禁止 TAS 跟踪区(数组)
*
* 标量参数用「字段描述表」集中登记(段名 + 键名 + 成员指针),避免逐项重复代码;
* 屏蔽区数组因为「长度由 *_num 决定」单独处理。加载流程:
* 解析 TOML -> 校验段/键集合 -> 读取标量与数组到临时对象 -> 取值校验 -> 一次性赋值。
* 任一步失败都不会修改调用方传入的 parameter。
************************************************************************************************/
namespace
{
// 屏蔽区数组容量,必须与 struct.h::RDP_parameter 中的数组长度一致。
constexpr int kProhibiteAreaCapacity = 30;
// 带上下文的错误收集:只记录第一条错误,统一返回 false 便于链式返回。
class LoadContext
{
public:
explicit LoadContext(std::string* error_msg) : error_msg_(error_msg) {}
bool fail(const std::string& message)
{
if (!failed_)
{
failed_ = true;
if (error_msg_ != nullptr)
*error_msg_ = message;
}
return false;
}
bool failed() const { return failed_; }
private:
std::string* error_msg_;
bool failed_ = false;
};
// 标量字段描述:TOML 段名、键名、RDP_parameter 成员指针。
struct FloatField
{
const char* section;
const char* key;
float RDP_parameter::*member;
};
struct IntField
{
const char* section;
const char* key;
int RDP_parameter::*member;
};
// ------------------------------ 标量字段登记表 ------------------------------
const FloatField kFloatFields[] = {
// [radar_params] 雷达工作参数
{"radar_params", "freq_0_GHz", &RDP_parameter::freq_0_GHz},
{"radar_params", "radar_height", &RDP_parameter::radar_height},
{"radar_params", "radar_system_delay", &RDP_parameter::radar_system_delay},
{"radar_params", "data_rate_tws", &RDP_parameter::data_rate_tws},
{"radar_params", "data_rate_tas", &RDP_parameter::data_rate_tas},
{"radar_params", "data_rate_track_only", &RDP_parameter::data_rate_track_only},
// [cohesion] 点迹凝聚参数
{"cohesion", "point_cohesion_range", &RDP_parameter::point_cohesion_range},
{"cohesion", "point_cohesion_velocity", &RDP_parameter::point_cohesion_velocity},
{"cohesion", "point_cohesion_azimuth", &RDP_parameter::point_cohesion_azimuth},
{"cohesion", "point_cohesion_range_min", &RDP_parameter::point_cohesion_range_min},
{"cohesion", "point_cohesion_range_max", &RDP_parameter::point_cohesion_range_max},
// [track_associate] 航迹关联参数
{"track_associate", "sigma_range", &RDP_parameter::sigma_range},
{"track_associate", "sigma_azimuth", &RDP_parameter::sigma_azimuth},
{"track_associate", "sigma_pitch", &RDP_parameter::sigma_pitch},
{"track_associate", "sigma_velocity", &RDP_parameter::sigma_velocity},
{"track_associate", "associate_threshold", &RDP_parameter::associate_threshold},
// [track_init] 航迹起始参数
{"track_init", "track_init_threshold", &RDP_parameter::track_init_threshold},
{"track_init", "track_init_angle", &RDP_parameter::track_init_angle},
{"track_init", "track_init_quality_L3", &RDP_parameter::track_init_quality_L3},
{"track_init", "track_init_quality_L4", &RDP_parameter::track_init_quality_L4},
{"track_init", "track_init_quality_L5", &RDP_parameter::track_init_quality_L5},
{"track_init", "track_init_quality_L6", &RDP_parameter::track_init_quality_L6},
{"track_init", "track_init_quality_L7", &RDP_parameter::track_init_quality_L7},
{"track_init", "track_init_velocity_min", &RDP_parameter::track_init_velocity_min},
{"track_init", "track_init_velocity_max", &RDP_parameter::track_init_velocity_max},
// [track_die] 航迹消亡参数
{"track_die", "track_die_quality", &RDP_parameter::track_die_quality},
{"track_die", "track_die_quality_pramary", &RDP_parameter::track_die_quality_pramary},
// [track_quality] 航迹质量管理参数
{"track_quality", "track_quality_max", &RDP_parameter::track_quality_max},
{"track_quality", "track_quality_min", &RDP_parameter::track_quality_min},
{"track_quality", "track_quality_d_weight", &RDP_parameter::track_quality_d_weight},
{"track_quality", "track_quality_snr_weight", &RDP_parameter::track_quality_snr_weight},
{"track_quality", "track_quality_len_weight", &RDP_parameter::track_quality_len_weight},
{"track_quality", "track_quality_iter_coeff", &RDP_parameter::track_quality_iter_coeff},
{"track_quality", "track_quality_decrease_coeff_o2", &RDP_parameter::track_quality_decrease_coeff_o2},
{"track_quality", "track_quality_decrease_coeff_o1", &RDP_parameter::track_quality_decrease_coeff_o1},
// [singer_model] singer 模型机动参数
{"singer_model", "model_1_Q_fast", &RDP_parameter::model_1_Q_fast},
{"singer_model", "model_1_Q_slow", &RDP_parameter::model_1_Q_slow},
{"singer_model", "model_2_Q_fast", &RDP_parameter::model_2_Q_fast},
{"singer_model", "model_2_Q_slow", &RDP_parameter::model_2_Q_slow},
{"singer_model", "model_3_Q_fast", &RDP_parameter::model_3_Q_fast},
{"singer_model", "model_3_Q_slow", &RDP_parameter::model_3_Q_slow},
};
const IntField kIntFields[] = {
// [radar_params] 雷达工作参数
{"radar_params", "radar_work_mode", &RDP_parameter::radar_work_mode},
{"radar_params", "scan_mode", &RDP_parameter::scan_mode},
// [track_associate] 航迹关联参数
{"track_associate", "extrapolate_max", &RDP_parameter::extrapolate_max},
{"track_associate", "extrapolate_max_tas", &RDP_parameter::extrapolate_max_tas},
{"track_associate", "height_smooth_win", &RDP_parameter::height_smooth_win},
// [track_init] 航迹起始参数
{"track_init", "track_init_point_num_min", &RDP_parameter::track_init_point_num_min},
// [track_die] 航迹消亡参数
{"track_die", "track_die_round", &RDP_parameter::track_die_round},
{"track_die", "track_die_round_pramary", &RDP_parameter::track_die_round_pramary},
{"track_die", "track_die_round_tas", &RDP_parameter::track_die_round_tas},
// [track_index] 航迹批号管理
{"track_index", "track_index_max", &RDP_parameter::track_index_max},
// [beam_control] 波束调度参数
{"beam_control", "default_track_beam_type", &RDP_parameter::default_track_beam_type},
{"beam_control", "tas_track_num_max", &RDP_parameter::tas_track_num_max},
// 屏蔽区数目(对应数组长度由它决定)
{"track_init_prohibite_area", "track_init_prohibite_area_num", &RDP_parameter::track_init_prohibite_area_num},
{"tas_prohibite_area", "tas_prohibite_area_num", &RDP_parameter::tas_prohibite_area_num},
};
// 屏蔽区数组描述:段名 + 键名 + 成员指针(数组长度为 kProhibiteAreaCapacity)。
struct AreaArrayField
{
const char* section;
const char* key;
float (RDP_parameter::*member)[kProhibiteAreaCapacity];
};
const AreaArrayField kAreaArrayFields[] = {
{"track_init_prohibite_area", "track_init_prohibite_area_range_min", &RDP_parameter::track_init_prohibite_area_range_min},
{"track_init_prohibite_area", "track_init_prohibite_area_range_max", &RDP_parameter::track_init_prohibite_area_range_max},
{"track_init_prohibite_area", "track_init_prohibite_area_azimuth_min", &RDP_parameter::track_init_prohibite_area_azimuth_min},
{"track_init_prohibite_area", "track_init_prohibite_area_azimuth_max", &RDP_parameter::track_init_prohibite_area_azimuth_max},
{"tas_prohibite_area", "tas_prohibite_area_range_min", &RDP_parameter::tas_prohibite_area_range_min},
{"tas_prohibite_area", "tas_prohibite_area_range_max", &RDP_parameter::tas_prohibite_area_range_max},
{"tas_prohibite_area", "tas_prohibite_area_azimuth_min", &RDP_parameter::tas_prohibite_area_azimuth_min},
{"tas_prohibite_area", "tas_prohibite_area_azimuth_max", &RDP_parameter::tas_prohibite_area_azimuth_max},
};
const char* const kAllSections[] = {
"radar_params",
"cohesion",
"track_associate",
"track_init",
"track_die",
"track_quality",
"track_index",
"beam_control",
"singer_model",
"track_init_prohibite_area",
"tas_prohibite_area",
};
// ------------------------------ 基础读取工具 ------------------------------
bool node_to_double(const toml::node& node, double& value)
{
if (node.is_integer())
{
value = static_cast<double>(*node.value<std::int64_t>());
return true;
}
if (node.is_floating_point())
{
value = *node.value<double>();
return true;
}
return false;
}
bool read_float(const toml::table& section, const char* section_name, const char* key, float& out, LoadContext& ctx)
{
const toml::node* node = section.get(key);
if (node == nullptr)
return ctx.fail("缺少配置项 [" + std::string(section_name) + "] " + key);
double value = 0.0;
if (!node_to_double(*node, value))
return ctx.fail("配置项 [" + std::string(section_name) + "] " + key + " 必须是数字");
if (!std::isfinite(value) || std::fabs(value) > static_cast<double>(FLT_MAX))
return ctx.fail("配置项 [" + std::string(section_name) + "] " + key + " 超出 float 可表示范围");
out = static_cast<float>(value);
return true;
}
bool read_int(const toml::table& section, const char* section_name, const char* key, int& out, LoadContext& ctx)
{
const toml::node* node = section.get(key);
if (node == nullptr)
return ctx.fail("缺少配置项 [" + std::string(section_name) + "] " + key);
if (!node->is_integer())
return ctx.fail("配置项 [" + std::string(section_name) + "] " + key + " 必须是整数");
const std::int64_t value = *node->value<std::int64_t>();
if (value < std::numeric_limits<int>::min() || value > std::numeric_limits<int>::max())
return ctx.fail("配置项 [" + std::string(section_name) + "] " + key + " 超出 int 可表示范围");
out = static_cast<int>(value);
return true;
}
// 取配置段;缺失或不是表都算错误(严格模式:11 个配置段全部必填)。
const toml::table* get_section(const toml::table& root, const char* section_name, LoadContext& ctx)
{
const toml::node* node = root.get(section_name);
if (node == nullptr)
{
ctx.fail("缺少配置段 [" + std::string(section_name) + "]");
return nullptr;
}
const toml::table* table = node->as_table();
if (table == nullptr)
{
ctx.fail("配置项 [" + std::string(section_name) + "] 必须是 TOML 表");
return nullptr;
}
return table;
}
// 收集某个段允许出现的全部键(标量 + 数组)。
std::vector<std::string_view> allowed_keys_of(const std::string_view section)
{
std::vector<std::string_view> keys;
for (const FloatField& field : kFloatFields)
{
if (section == field.section)
keys.emplace_back(field.key);
}
for (const IntField& field : kIntFields)
{
if (section == field.section)
keys.emplace_back(field.key);
}
for (const AreaArrayField& field : kAreaArrayFields)
{
if (section == field.section)
keys.emplace_back(field.key);
}
return keys;
}
// 严格校验某个段中只出现允许的键(拼错的键按未知参数拒绝)。
bool check_keys_of_section(const toml::table& section, const char* section_name, LoadContext& ctx)
{
const std::vector<std::string_view> allowed = allowed_keys_of(section_name);
for (const auto& [key, value] : section)
{
(void)value;
bool known = false;
for (const std::string_view item : allowed)
{
if (key.str() == item)
{
known = true;
break;
}
}
if (!known)
return ctx.fail("配置段 [" + std::string(section_name) + "] 中存在未知配置项: " + std::string(key.str()));
}
return true;
}
// 读取一个屏蔽区段的 4 个数组。
// 约定:*_num > 0 时数组必须存在且元素个数等于 *_num;*_num == 0 时数组可省略,
// 存在时必须是空数组。数组元素写入 RDP_parameter 的定长数组(未使用位置保持 0)。
bool read_area_arrays(const toml::table& section, const char* section_name, int area_num,
RDP_parameter& parameter, LoadContext& ctx)
{
for (const AreaArrayField& field : kAreaArrayFields)
{
if (std::string_view(field.section) != section_name)
continue;
const toml::node* node = section.get(field.key);
if (node == nullptr)
{
if (area_num == 0)
continue;
return ctx.fail("缺少配置项 [" + std::string(section_name) + "] " + field.key);
}
const toml::array* array = node->as_array();
if (array == nullptr)
return ctx.fail("配置项 [" + std::string(section_name) + "] " + field.key + " 必须是数组");
if (static_cast<int>(array->size()) != area_num)
return ctx.fail("配置项 [" + std::string(section_name) + "] " + field.key + " 的元素个数(" +
std::to_string(array->size()) + ")与 " + std::string(section_name) + "_num(" +
std::to_string(area_num) + ")不一致");
float* destination = parameter.*(field.member);
int index = 0;
for (const toml::node& element : *array)
{
double value = 0.0;
if (!node_to_double(element, value))
return ctx.fail("配置项 [" + std::string(section_name) + "] " + field.key + " 的第 " +
std::to_string(index + 1) + " 个元素必须是数字");
if (!std::isfinite(value) || std::fabs(value) > static_cast<double>(FLT_MAX))
return ctx.fail("配置项 [" + std::string(section_name) + "] " + field.key + " 的第 " +
std::to_string(index + 1) + " 个元素超出 float 可表示范围");
destination[index] = static_cast<float>(value);
++index;
}
}
return true;
}
// ------------------------------ 取值校验 ------------------------------
bool validate_parameter(const RDP_parameter& p, LoadContext& ctx)
{
// 雷达工作参数
if (p.radar_work_mode < 0 || p.radar_work_mode > 2)
return ctx.fail("参数 radar_work_mode 必须在 0~2 之间(实际 " + std::to_string(p.radar_work_mode) + ")");
if (p.scan_mode != 0 && p.scan_mode != 1)
return ctx.fail("参数 scan_mode 必须为 0(相扫模式)或 1(机扫模式)(实际 " + std::to_string(p.scan_mode) + ")");
if (!(p.freq_0_GHz > 0.0f))
return ctx.fail("参数 freq_0_GHz 必须大于 0");
if (p.radar_system_delay < 0.0f)
return ctx.fail("参数 radar_system_delay 不能为负");
if (!(p.data_rate_tws > 0.0f))
return ctx.fail("参数 data_rate_tws 必须大于 0");
if (!(p.data_rate_tas > 0.0f))
return ctx.fail("参数 data_rate_tas 必须大于 0");
if (p.data_rate_track_only < 0.0f)
return ctx.fail("参数 data_rate_track_only 不能为负");
// 点迹凝聚参数
if (!(p.point_cohesion_range > 0.0f))
return ctx.fail("参数 point_cohesion_range 必须大于 0");
if (!(p.point_cohesion_velocity > 0.0f))
return ctx.fail("参数 point_cohesion_velocity 必须大于 0");
if (!(p.point_cohesion_azimuth > 0.0f))
return ctx.fail("参数 point_cohesion_azimuth 必须大于 0");
if (p.point_cohesion_range_min < 0.0f)
return ctx.fail("参数 point_cohesion_range_min 不能为负");
if (!(p.point_cohesion_range_max > p.point_cohesion_range_min))
return ctx.fail("参数 point_cohesion_range_max 必须大于 point_cohesion_range_min");
// 航迹关联参数
if (!(p.sigma_range > 0.0f))
return ctx.fail("参数 sigma_range 必须大于 0");
if (!(p.sigma_azimuth > 0.0f))
return ctx.fail("参数 sigma_azimuth 必须大于 0");
if (!(p.sigma_pitch > 0.0f))
return ctx.fail("参数 sigma_pitch 必须大于 0");
if (!(p.sigma_velocity > 0.0f))
return ctx.fail("参数 sigma_velocity 必须大于 0");
if (!(p.associate_threshold > 0.0f))
return ctx.fail("参数 associate_threshold 必须大于 0");
if (p.extrapolate_max < 0)
return ctx.fail("参数 extrapolate_max 不能为负");
if (p.extrapolate_max_tas < 0)
return ctx.fail("参数 extrapolate_max_tas 不能为负");
if (p.height_smooth_win < 1)
return ctx.fail("参数 height_smooth_win 必须大于等于 1");
// 航迹起始参数
if (!(p.track_init_threshold > 0.0f))
return ctx.fail("参数 track_init_threshold 必须大于 0");
if (!(p.track_init_angle > 0.0f) || p.track_init_angle > 180.0f)
return ctx.fail("参数 track_init_angle 必须在 (0, 180] 度范围内");
if (p.track_init_point_num_min < 3 || p.track_init_point_num_min > 9)
return ctx.fail("参数 track_init_point_num_min 必须在 3~9 之间(实际 " +
std::to_string(p.track_init_point_num_min) + ")");
const float init_quality[] = {
p.track_init_quality_L3, p.track_init_quality_L4, p.track_init_quality_L5,
p.track_init_quality_L6, p.track_init_quality_L7};
for (int i = 0; i < 5; ++i)
{
if (init_quality[i] < 0.0f || init_quality[i] > 1.0f)
return ctx.fail("参数 track_init_quality_L" + std::to_string(i + 3) + " 必须在 0~1 之间");
}
if (p.track_init_velocity_min < 0.0f)
return ctx.fail("参数 track_init_velocity_min 不能为负");
if (!(p.track_init_velocity_max > p.track_init_velocity_min))
return ctx.fail("参数 track_init_velocity_max 必须大于 track_init_velocity_min");
// 航迹消亡参数
if (p.track_die_round < 0)
return ctx.fail("参数 track_die_round 不能为负");
if (p.track_die_round_pramary < 0)
return ctx.fail("参数 track_die_round_pramary 不能为负");
if (p.track_die_round_tas < 0)
return ctx.fail("参数 track_die_round_tas 不能为负");
if (p.track_die_quality < 0.0f || p.track_die_quality > 1.0f)
return ctx.fail("参数 track_die_quality 必须在 0~1 之间");
if (p.track_die_quality_pramary < 0.0f || p.track_die_quality_pramary > 1.0f)
return ctx.fail("参数 track_die_quality_pramary 必须在 0~1 之间");
// 航迹质量管理参数
if (p.track_quality_min < 0.0f)
return ctx.fail("参数 track_quality_min 不能为负");
if (!(p.track_quality_max > p.track_quality_min))
return ctx.fail("参数 track_quality_max 必须大于 track_quality_min");
if (p.track_quality_d_weight < 0.0f || p.track_quality_snr_weight < 0.0f || p.track_quality_len_weight < 0.0f)
return ctx.fail("参数 track_quality_d_weight / track_quality_snr_weight / track_quality_len_weight 不能为负");
if (p.track_quality_iter_coeff < 0.0f || p.track_quality_iter_coeff > 1.0f)
return ctx.fail("参数 track_quality_iter_coeff 必须在 0~1 之间");
if (p.track_quality_decrease_coeff_o2 > 0.0f || p.track_quality_decrease_coeff_o1 > 0.0f)
return ctx.fail("参数 track_quality_decrease_coeff_o2 / track_quality_decrease_coeff_o1 必须小于等于 0");
// 航迹批号管理
if (p.track_index_max < 1 || p.track_index_max > MAX_TRACK_NUM)
return ctx.fail("参数 track_index_max 必须在 1~" + std::to_string(MAX_TRACK_NUM) + " 之间(实际 " +
std::to_string(p.track_index_max) + ")");
// 波束调度参数
if (p.default_track_beam_type < 0)
return ctx.fail("参数 default_track_beam_type 不能为负");
if (p.tas_track_num_max < 0 || p.tas_track_num_max > RDP_TAS_QUEUE_CAPACITY)
return ctx.fail("参数 tas_track_num_max 必须在 0~" + std::to_string(RDP_TAS_QUEUE_CAPACITY) +
" 之间(实际 " + std::to_string(p.tas_track_num_max) + ")");
// singer 模型机动参数
const float model_q[] = {
p.model_1_Q_fast, p.model_1_Q_slow, p.model_2_Q_fast,
p.model_2_Q_slow, p.model_3_Q_fast, p.model_3_Q_slow};
const char* const model_q_names[] = {
"model_1_Q_fast", "model_1_Q_slow", "model_2_Q_fast",
"model_2_Q_slow", "model_3_Q_fast", "model_3_Q_slow"};
for (int i = 0; i < 6; ++i)
{
if (!(model_q[i] > 0.0f))
return ctx.fail(std::string("参数 ") + model_q_names[i] + " 必须大于 0");
}
// 屏蔽区
if (p.track_init_prohibite_area_num < 0 || p.track_init_prohibite_area_num > kProhibiteAreaCapacity)
return ctx.fail("参数 track_init_prohibite_area_num 必须在 0~" +
std::to_string(kProhibiteAreaCapacity) + " 之间");
if (p.tas_prohibite_area_num < 0 || p.tas_prohibite_area_num > kProhibiteAreaCapacity)
return ctx.fail("参数 tas_prohibite_area_num 必须在 0~" + std::to_string(kProhibiteAreaCapacity) + " 之间");
for (int i = 0; i < p.track_init_prohibite_area_num; ++i)
{
const std::string prefix = "track_init_prohibite_area 第 " + std::to_string(i + 1) + " 区";
if (p.track_init_prohibite_area_range_min[i] < 0.0f ||
!(p.track_init_prohibite_area_range_max[i] > p.track_init_prohibite_area_range_min[i]))
return ctx.fail(prefix + " 的距离范围非法(要求 max > min >= 0)");
if (p.track_init_prohibite_area_azimuth_min[i] < 0.0f ||
p.track_init_prohibite_area_azimuth_max[i] > 360.0f ||
!(p.track_init_prohibite_area_azimuth_max[i] > p.track_init_prohibite_area_azimuth_min[i]))
return ctx.fail(prefix + " 的方位范围非法(要求 0 =< min < max <= 360)");
}
for (int i = 0; i < p.tas_prohibite_area_num; ++i)
{
const std::string prefix = "tas_prohibite_area 第 " + std::to_string(i + 1) + " 区";
if (p.tas_prohibite_area_range_min[i] < 0.0f ||
!(p.tas_prohibite_area_range_max[i] > p.tas_prohibite_area_range_min[i]))
return ctx.fail(prefix + " 的距离范围非法(要求 max > min >= 0)");
if (p.tas_prohibite_area_azimuth_min[i] < 0.0f ||
p.tas_prohibite_area_azimuth_max[i] > 360.0f ||
!(p.tas_prohibite_area_azimuth_max[i] > p.tas_prohibite_area_azimuth_min[i]))
return ctx.fail(prefix + " 的方位范围非法(要求 0 =< min < max <= 360)");
}
return true;
}
// ------------------------------ 加载主流程 ------------------------------
bool load_all_sections(const toml::table& root, RDP_parameter& parameter, LoadContext& ctx)
{
// 1) 顶层只允许已知配置段
for (const auto& [key, value] : root)
{
(void)value;
bool known = false;
for (const char* name : kAllSections)
{
if (key.str() == name)
{
known = true;
break;
}
}
if (!known)
return ctx.fail("配置文件中存在未知配置段: [" + std::string(key.str()) + "]");
}
// 2) 先确认 11 个配置段都存在且为表(缺段优先报错,便于定位)
for (const char* name : kAllSections)
{
if (get_section(root, name, ctx) == nullptr)
return false;
}
// 3) 再校验每段内只允许登记过的键
for (const char* name : kAllSections)
{
const toml::table* section = get_section(root, name, ctx);
if (section == nullptr)
return false;
if (!check_keys_of_section(*section, name, ctx))
return false;
}
// 4) 读取全部标量
for (const FloatField& field : kFloatFields)
{
const toml::table* section = get_section(root, field.section, ctx);
if (section == nullptr)
return false;
if (!read_float(*section, field.section, field.key, parameter.*(field.member), ctx))
return false;
}
for (const IntField& field : kIntFields)
{
const toml::table* section = get_section(root, field.section, ctx);
if (section == nullptr)
return false;
if (!read_int(*section, field.section, field.key, parameter.*(field.member), ctx))
return false;
}
// 4) 读取屏蔽区数组(长度依赖已读入的 *_num)
const toml::table* init_area = get_section(root, "track_init_prohibite_area", ctx);
if (init_area == nullptr)
return false;
if (!read_area_arrays(*init_area, "track_init_prohibite_area", parameter.track_init_prohibite_area_num, parameter, ctx))
return false;
const toml::table* tas_area = get_section(root, "tas_prohibite_area", ctx);
if (tas_area == nullptr)
return false;
if (!read_area_arrays(*tas_area, "tas_prohibite_area", parameter.tas_prohibite_area_num, parameter, ctx))
return false;
return true;
}
} // namespace
int rdp_parameter_load(const char* parameter_file_path, RDP_parameter& parameter, std::string* error_msg)
{
LoadContext ctx(error_msg);
if (parameter_file_path == nullptr || parameter_file_path[0] == '\0')
{
ctx.fail("配置文件路径为空");
return -1;
}
if (error_msg != nullptr)
error_msg->clear();
toml::table root;
try
{
root = toml::parse_file(parameter_file_path);
}
catch (const toml::parse_error& err)
{
ctx.fail(std::string("TOML 解析失败: ") + std::string(err.what()));
return -1;
}
catch (const std::exception& err)
{
ctx.fail(std::string("读取配置文件失败: ") + err.what());
return -1;
}
RDP_parameter loaded{};
if (!load_all_sections(root, loaded, ctx))
return -1;
if (!validate_parameter(loaded, ctx))
return -1;
// 全部校验通过后才一次性写入,保证失败时调用方参数不变。
parameter = loaded;
return 0;
}
@@ -0,0 +1,26 @@
#ifndef RDP_PARAMETER_LOADER_H
#define RDP_PARAMETER_LOADER_H
#pragma once
#include "struct.h"
#include <string>
/************************************************************************************************
* 雷达数据处理参数加载器(内部模块,不属于宿主接口)
*
* 功能:把 TOML 配置文件加载到 struct.h::RDP_parameter。
* 约定:
* 1) 配置文件的每个配置段与 RDP_parameter 的注释分组对应,配置键名与结构体成员名完全一致;
* 2) 屏蔽区(track_init_prohibite_area / tas_prohibite_area)的数组使用与成员同名的 TOML 数组,
* 数组长度必须等于对应的 *_num,且 *_num 不超过 30;
* 3) 所有标量键均必须存在且类型/取值合法(严格校验),任一错误返回 -1 且不修改 parameter;
* 4) 数组键在 *_num == 0 时可以省略,存在时必须为空数组;*_num > 0 时必须存在且长度等于 *_num。
*
* 参数:
* parameter_file_path - 配置文件路径(UTF-8,宿主进程当前工作目录可达)
* parameter - 输出参数;仅在成功时被写入
* error_msg - 可选输出;失败时写入可读的错误原因(UTF-8)
* 返回:成功返回 0;失败返回 -1
************************************************************************************************/
int rdp_parameter_load(const char* parameter_file_path, RDP_parameter& parameter, std::string* error_msg);
#endif // RDP_PARAMETER_LOADER_H
+774 -58
View File
@@ -2,19 +2,45 @@
// 本文件仅在构建系统定义 RDP_RECORD_ENABLE(CMake 选项 RDP_DATA_RECORD 开启)时参与编译,
// 且以 C++20 编译(ormpp 要求);雷达处理代码仍为 C++17,二者通过 rdp_record.h 的
// 「仅声明 + 显式实例化」跨标准边界,详见 docs/data_record_debug.md。
//
// 性能模型(V1.5.8 优化):
// 处理线程:记录点只做「收集源结构体 → 一次入队」(拷贝 + 互斥队列 push,不阻塞);
// 写线程 :从有界队列取批 → 构造记录实体(数组转文本在这里)→ ormpp 批量事务写 SQLite;
// 数据库 :WAL + synchronous=OFF + 8MB 页缓存 + busy_timeout;建表去 AUTOINCREMENT;
// 队列满 :整批丢弃并计数(实时性优先),Shutdown 时排空并打印统计。
#ifdef RDP_RECORD_ENABLE
//注意:windows.h 必须在业务/实体头文件之前包含 —— struct.h 在全局作用域有
//"using namespace std;",若之后再包含 windows.h,其 byte 类型会与 std::byte 产生歧义。
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h> //仅用于把记录写线程优先级降到 BELOW_NORMAL,避免与雷达处理线程争抢 CPU
#endif
#include "rdp_record.h"
#include "rdp_record_entity.h"
#include "parameters.h"
#include "rdp_log.h"
#include <atomic>
#include <chrono>
#include <climits>
#include <condition_variable>
#include <cstdint>
#include <deque>
#include <direct.h>
#include <io.h>
#include <iterator>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <time.h>
#include <tuple>
#include <utility>
#include <vector>
//第三方库(ormpp / iguana)模板代码在 /W3 下可能产生告警,按 third_party/ormpp/UPSTREAM.md
@@ -31,27 +57,86 @@
namespace
{
//============================== 基础类型 ==============================
enum class BatchKind : std::uint8_t
{
Point, //RdpRecPoint(输入点迹)
CohesionTws, //RdpRecPointCohesionTWS
CohesionTas, //RdpRecPointCohesionTAS
TempTrack, //RdpRecTempTrack
TrustTrack, //RdpRecTrustTrack
AssoInfo //RdpRecAssoInfo
};
//一批入队数据(一种记录表的一批源结构体;处理线程收集后整体 move 进队列)
struct Batch
{
BatchKind kind = BatchKind::Point;
std::vector<PointRecv> points; //Point/CohesionTws/CohesionTas 共用
std::vector<Temp_track> temp_tracks;
std::vector<Trust_Track> trust_tracks;
std::vector<Asso_Info> asso_infos;
};
//写线程内的待提交缓冲(按表分别累积,达到批大小或超时后各表分别提交)
struct Pending
{
std::vector<PointRecv> points;
std::vector<PointRecv> cohesion_tws;
std::vector<PointRecv> cohesion_tas;
std::vector<Temp_track> temp_tracks;
std::vector<Trust_Track> trust_tracks;
std::vector<Asso_Info> asso_infos;
};
//统计计数器(处理线程/写线程并发读写)
struct Counters
{
std::atomic<std::uint64_t> enqueued_rows{0};
std::atomic<std::uint64_t> dropped_rows{0};
std::atomic<std::uint64_t> written_rows{0};
std::atomic<std::uint64_t> transactions{0};
std::atomic<std::uint64_t> write_errors{0};
std::atomic<std::uint64_t> queue_peak_bytes{0};
std::atomic<std::uint64_t> queue_peak_rows{0};
};
//记录模块内部状态(Pimpl:ormpp / SQLite 类型不出现在 rdp_record.h 中)
struct Record_State
{
ormpp::dbng<ormpp::sqlite> db; //SQLite 连接(单连接,写入时串行使用)
ormpp::dbng<ormpp::sqlite> db; //SQLite 连接(写线程独占;遗留同步接口经 db_mtx 串行)
std::string path; //当前 db 文件全路径
bool ready; //是否已成功打开并完成初始化
bool failed; //初始化失败:后续写入全部忽略,不再反复报错
std::mutex mtx; //写入串行化(宿主多线程调用时保护单连接)
std::atomic<bool> ready{false};
std::atomic<bool> failed{false};
Record_State() : ready(false), failed(false) {}
std::mutex db_mtx; //串行化数据库访问(写线程 + 遗留同步写)
std::map<std::string, int> tables; //表状态:0 未建 / 1 可用 / 2 失败(db_mtx 保护)
std::mutex q_mtx; //队列锁
std::condition_variable q_cv; //队列条件变量
std::deque<Batch> queue; //有界入队队列
std::size_t queued_bytes = 0;
std::size_t queued_rows = 0;
std::size_t queued_batches = 0;
bool stopping = false;
std::thread writer; //记录写线程
Counters counters;
std::atomic<int> consecutive_errors{0};
~Record_State();
};
std::unique_ptr<Record_State>& State()
{
//函数内静态:宿主未调用 Data_Process_Factory::Destroy() 时由析构兜底关库
//函数内静态:宿主未调用 Data_Process_Factory::Destroy() 时由析构兜底停止写线程/关库
static std::unique_ptr<Record_State> s;
return s;
}
//============================== 通用小工具 ==============================
//当前本地时间字符串,格式 "YYYYMMDD_HHMMSS"(与 rdp_log 的日志文件命名风格一致)
bool TimeStamp(char* buf, size_t size)
bool TimeStamp(char* buf, std::size_t size)
{
time_t now = time(NULL);
struct tm local;
@@ -62,39 +147,512 @@ namespace
return strftime(buf, size, "%Y%m%d_%H%M%S", &local) != 0;
}
//写入失败提示(「每个实体只提示一次」由调用方的静态标志控制)
void ReportError(Record_State& st, const char* what)
//写入失败提示
void ReportError(Record_State& st, const char* what, const std::string& detail)
{
RDP_LOG << "[rdp_record] " << what << " failed: " << st.db.get_last_error() << std::endl;
RDP_LOG_ERROR("[rdp_record] {} failed ({}): {}", what, detail, st.db.get_last_error());
}
//确保实体的数据表存在:每个实体类型只尝试建表一次(失败后该实体永久跳过)
//注意:ormpp 的 ormpp_auto_key 定义在全局命名空间(third_party/ormpp/ormpp/entity.hpp),
// 传该参数可在建表时登记自增主键,使后续插入自动跳过 id 列
template <typename T>
bool EnsureTable(Record_State& st)
void LogStats(Record_State& st, const char* tag)
{
static const bool created = st.db.create_datatable<T>(::ormpp_auto_key{"id"});
return created;
RDP_LOG_INFO("[rdp_record] stats({}): enqueued_rows={}, written_rows={}, dropped_rows={}, "
"transactions={}, write_errors={}, queue_peak_bytes={}, queue_peak_rows={}",
tag,
st.counters.enqueued_rows.load(),
st.counters.written_rows.load(),
st.counters.dropped_rows.load(),
st.counters.transactions.load(),
st.counters.write_errors.load(),
st.counters.queue_peak_bytes.load(),
st.counters.queue_peak_rows.load());
}
//写入前置处理:自动建表;返回 true 表示可以向该表写入
template <typename T>
bool PrepareWrite(Record_State& st)
std::string TableName()
{
static bool reported = false;
if (EnsureTable<T>(st))
//ormpp 以「短结构体名」作为表名
return std::string(ormpp::get_short_struct_name<T>());
}
//============================== 记录批次辅助 ==============================
template <typename T>
std::size_t BatchRowsOf(const std::vector<T>& v)
{
return v.size();
}
std::size_t BatchRows(const Batch& b)
{
return b.points.size() + b.temp_tracks.size() + b.trust_tracks.size() + b.asso_infos.size();
}
std::size_t BatchBytes(const Batch& b)
{
//近似统计(用于有界队列的字节上限;容器容量 + 对象本身)
std::size_t bytes = sizeof(Batch);
bytes += b.points.capacity() * sizeof(PointRecv);
bytes += b.temp_tracks.capacity() * sizeof(Temp_track);
bytes += b.trust_tracks.capacity() * sizeof(Trust_Track);
bytes += b.asso_infos.capacity() * sizeof(Asso_Info);
return bytes;
}
std::size_t PendingRows(const Pending& p)
{
return p.points.size() + p.cohesion_tws.size() + p.cohesion_tas.size()
+ p.temp_tracks.size() + p.trust_tracks.size() + p.asso_infos.size();
}
void ClearPending(Pending& p)
{
p.points.clear();
p.cohesion_tws.clear();
p.cohesion_tas.clear();
p.temp_tracks.clear();
p.trust_tracks.clear();
p.asso_infos.clear();
}
template <typename T>
void AppendMove(std::vector<T>& dst, std::vector<T>& src)
{
if (src.empty())
{
return;
}
if (dst.empty())
{
dst = std::move(src);
return;
}
dst.insert(dst.end(), std::make_move_iterator(src.begin()), std::make_move_iterator(src.end()));
src.clear();
}
void AppendBatch(Pending& p, Batch& b)
{
switch (b.kind)
{
case BatchKind::Point: AppendMove(p.points, b.points); break;
case BatchKind::CohesionTws: AppendMove(p.cohesion_tws, b.points); break;
case BatchKind::CohesionTas: AppendMove(p.cohesion_tas, b.points); break;
case BatchKind::TempTrack: AppendMove(p.temp_tracks, b.temp_tracks); break;
case BatchKind::TrustTrack: AppendMove(p.trust_tracks, b.trust_tracks); break;
case BatchKind::AssoInfo: AppendMove(p.asso_infos, b.asso_infos); break;
}
}
//============================== 数据库初始化/建表 ==============================
//应用 SQLite PRAGMA(失败只记录,不阻止记录功能;调试用途)
void ApplyPragmas(Record_State& st)
{
#if RDP_RECORD_SQLITE_WAL
try
{
//PRAGMA journal_mode 会返回一行,ormpp 的 execute() 只接受不返回结果行的语句,
//因此用 query_s<std::tuple<...>> 消费返回行。
auto rows = st.db.query_s<std::tuple<std::string>>("PRAGMA journal_mode=WAL;");
std::string mode = rows.empty() ? std::string("<none>") : std::get<0>(rows[0]);
RDP_LOG_INFO("[rdp_record] journal_mode={}", mode);
}
catch (...)
{
RDP_LOG_WARN("[rdp_record] set journal_mode=WAL failed, fallback to default journal");
}
#endif
if (!st.db.execute("PRAGMA synchronous=OFF;"))
{
RDP_LOG_WARN("[rdp_record] set synchronous=OFF failed");
}
const std::string cache = "PRAGMA cache_size=-" + std::to_string(RDP_RECORD_SQLITE_CACHE_KB) + ";";
if (!st.db.execute(cache))
{
RDP_LOG_WARN("[rdp_record] set cache_size failed");
}
try
{
//busy_timeout 也返回一行
st.db.query_s<std::tuple<int>>(
"PRAGMA busy_timeout=" + std::to_string(RDP_RECORD_SQLITE_BUSY_TIMEOUT_MS) + ";");
}
catch (...)
{
RDP_LOG_WARN("[rdp_record] set busy_timeout failed");
}
}
//建表 + 去掉 AUTOINCREMENT(本项目每次运行新建库、只增不删,普通 INTEGER PRIMARY KEY 即可;
//去 AUTOINCREMENT 可省掉每行对 sqlite_sequence 的读写。ormpp 的 DDL 硬编码 AUTOINCREMENT,
//这里先用 ormpp 建表(同时登记自增主键映射),再按 sqlite_master 中的 DDL 去关键字重建空表。)
template <typename T>
bool CreateTable(Record_State& st)
{
if (!st.db.create_datatable<T>(::ormpp_auto_key{"id"}))
{
return false;
}
const std::string table = TableName<T>();
std::string ddl;
try
{
auto rows = st.db.query_s<std::tuple<std::string>>(
"select sql from sqlite_master where type='table' and name='" + table + "'");
if (!rows.empty())
{
ddl = std::get<0>(rows[0]);
}
}
catch (...)
{
return true; //查询失败不影响记录,保留 ormpp 建出的表
}
if (ddl.find("AUTOINCREMENT") == std::string::npos)
{
return true; //ormpp 版本变化后不再带 AUTOINCREMENT,直接使用
}
std::string stripped = ddl;
const std::string keyword = "AUTOINCREMENT";
for (std::size_t pos = stripped.find(keyword); pos != std::string::npos;
pos = stripped.find(keyword))
{
stripped.erase(pos, keyword.size());
}
if (st.db.execute("DROP TABLE \"" + table + "\";") &&
st.db.execute(stripped))
{
RDP_LOG_INFO("[rdp_record] table {} created without AUTOINCREMENT", table);
return true;
}
if (!reported)
//回退:若重建失败(极端情况),尽量恢复一张可用表,保证记录功能继续
if (st.db.create_datatable<T>(::ormpp_auto_key{"id"}))
{
reported = true;
ReportError(st, "create table");
RDP_LOG_WARN("[rdp_record] warning: keep AUTOINCREMENT for {}", table);
return true;
}
return false;
}
//写入前置处理(调用方需持有 db_mtx):按表缓存建表结果,失败的表永久跳过
template <typename T>
bool PrepareWrite(Record_State& st)
{
const std::string table = TableName<T>();
std::map<std::string, int>::iterator it = st.tables.find(table);
const int state = (it == st.tables.end()) ? 0 : it->second;
if (state == 2)
{
return false;
}
if (state == 1)
{
return true;
}
if (CreateTable<T>(st))
{
st.tables[table] = 1;
return true;
}
st.tables[table] = 2;
ReportError(st, "create table", table);
return false;
}
//批量写入实体(一次 BEGIN/COMMIT 事务;调用方无需持锁)
template <typename T>
bool InsertEntities(Record_State& st, const std::vector<T>& v)
{
if (v.empty())
{
return true;
}
std::lock_guard<std::mutex> lock(st.db_mtx);
if (st.failed.load())
{
return false;
}
if (!PrepareWrite<T>(st))
{
return false;
}
const int rows = st.db.insert(v);
if (rows == INT_MIN)
{
st.counters.write_errors.fetch_add(1);
st.tables[TableName<T>()] = 2;
const int errors = st.consecutive_errors.fetch_add(1) + 1;
ReportError(st, "insert", TableName<T>());
if (errors >= RDP_RECORD_MAX_CONSECUTIVE_ERRORS)
{
st.failed.store(true);
RDP_LOG_ERROR("[rdp_record] too many consecutive write errors, recording stopped");
}
return false;
}
st.consecutive_errors.store(0);
st.counters.written_rows.fetch_add(v.size());
st.counters.transactions.fetch_add(1);
return true;
}
//按批大小分块构造实体并提交(实体构造 = 数组转文本,全部在写线程完成)
template <typename Entity, typename Source>
bool FlushKind(Record_State& st, std::vector<Source>& src)
{
if (src.empty())
{
return true;
}
const std::size_t chunk = (RDP_RECORD_WRITE_BATCH_ROWS == 0)
? 4096
: static_cast<std::size_t>(RDP_RECORD_WRITE_BATCH_ROWS);
bool ok = true;
for (std::size_t pos = 0; pos < src.size() && !st.failed.load();)
{
std::size_t n = src.size() - pos;
if (n > chunk)
{
n = chunk;
}
std::vector<Entity> entities;
entities.reserve(n);
for (std::size_t i = 0; i < n; ++i)
{
entities.emplace_back(src[pos + i]); //Entity(Source&) 构造:数组转文本在此完成
}
if (!InsertEntities<Entity>(st, entities))
{
ok = false;
}
pos += n;
}
src.clear();
return ok;
}
bool FlushPending(Record_State& st, Pending& p)
{
bool ok = true;
ok &= FlushKind<RdpRecPoint>(st, p.points);
ok &= FlushKind<RdpRecPointCohesionTWS>(st, p.cohesion_tws);
ok &= FlushKind<RdpRecPointCohesionTAS>(st, p.cohesion_tas);
ok &= FlushKind<RdpRecTempTrack>(st, p.temp_tracks);
ok &= FlushKind<RdpRecTrustTrack>(st, p.trust_tracks);
ok &= FlushKind<RdpRecAssoInfo>(st, p.asso_infos);
ClearPending(p);
return ok;
}
//============================== 入队/写线程 ==============================
void LogDroppedRows(std::uint64_t total_dropped, std::uint64_t newly_dropped)
{
const std::uint64_t interval = (RDP_RECORD_DROP_LOG_INTERVAL == 0)
? 0
: static_cast<std::uint64_t>(RDP_RECORD_DROP_LOG_INTERVAL);
if (interval == 0)
{
return;
}
if ((total_dropped / interval) != ((total_dropped - newly_dropped) / interval))
{
RDP_LOG_WARN("[rdp_record] queue full, dropped rows total={}", total_dropped);
}
}
void EnqueueBatch(Batch&& b)
{
const std::size_t rows = BatchRows(b);
if (rows == 0)
{
return;
}
std::unique_ptr<Record_State>& sp = State();
if (sp.get() == 0)
{
return;
}
if (!sp->ready || sp->failed.load())
{
sp->counters.dropped_rows.fetch_add(rows);
return;
}
const std::size_t bytes = BatchBytes(b);
std::lock_guard<std::mutex> lock(sp->q_mtx);
if (sp->stopping || sp->failed.load())
{
const std::uint64_t total = sp->counters.dropped_rows.fetch_add(rows) + rows;
LogDroppedRows(total, rows);
return;
}
if (sp->queued_batches + 1 > static_cast<std::size_t>(RDP_RECORD_QUEUE_MAX_BATCHES) ||
sp->queued_bytes + bytes > static_cast<std::size_t>(RDP_RECORD_QUEUE_CAP_BYTES))
{
const std::uint64_t total = sp->counters.dropped_rows.fetch_add(rows) + rows;
LogDroppedRows(total, rows);
return;
}
sp->queue.push_back(std::move(b));
sp->queued_batches += 1;
sp->queued_bytes += bytes;
sp->queued_rows += rows;
sp->counters.enqueued_rows.fetch_add(rows);
if (sp->counters.queue_peak_bytes.load() < sp->queued_bytes)
{
sp->counters.queue_peak_bytes.store(sp->queued_bytes);
}
if (sp->counters.queue_peak_rows.load() < sp->queued_rows)
{
sp->counters.queue_peak_rows.store(sp->queued_rows);
}
sp->q_cv.notify_one();
}
void DropQueued(Record_State& st)
{
std::size_t rows = 0;
{
std::lock_guard<std::mutex> lock(st.q_mtx);
for (std::size_t i = 0; i < st.queue.size(); ++i)
{
rows += BatchRows(st.queue[i]);
}
st.queue.clear();
st.queued_bytes = 0;
st.queued_rows = 0;
st.queued_batches = 0;
}
if (rows > 0)
{
const std::uint64_t total = st.counters.dropped_rows.fetch_add(rows) + rows;
RDP_LOG_INFO("[rdp_record] writer stopped, dropped queued rows={}, total dropped={}", rows, total);
}
}
void WriterLoopBody(Record_State& st)
{
Pending pending;
for (;;)
{
std::deque<Batch> local;
{
std::unique_lock<std::mutex> lock(st.q_mtx);
st.q_cv.wait(lock, [&st]() { return !st.queue.empty() || st.stopping; });
if (st.queue.empty() && st.stopping)
{
break; //停止且队列已空:退出(pending 已在上一轮提交)
}
local.swap(st.queue);
st.queued_bytes = 0;
st.queued_rows = 0;
st.queued_batches = 0;
}
for (std::size_t i = 0; i < local.size(); ++i)
{
AppendBatch(pending, local[i]);
}
local.clear();
//凑批:未达目标且未停止时,短暂等待更多数据,减少事务次数
while (!st.stopping && !st.failed.load() &&
PendingRows(pending) < static_cast<std::size_t>(RDP_RECORD_WRITE_BATCH_ROWS))
{
std::unique_lock<std::mutex> lock(st.q_mtx);
const bool more = st.q_cv.wait_for(
lock,
std::chrono::milliseconds(RDP_RECORD_WRITE_MAX_WAIT_MS),
[&st]() { return !st.queue.empty() || st.stopping; });
if (!more)
{
break; //超时:提交现有数据
}
if (st.queue.empty())
{
break; //停止且队列已空
}
local.swap(st.queue);
st.queued_bytes = 0;
st.queued_rows = 0;
st.queued_batches = 0;
lock.unlock();
for (std::size_t i = 0; i < local.size(); ++i)
{
AppendBatch(pending, local[i]);
}
local.clear();
}
FlushPending(st, pending);
if (st.failed.load())
{
DropQueued(st);
return;
}
}
FlushPending(st, pending);
}
void WriterLoop(Record_State& st)
{
#ifdef _WIN32
//记录写线程是后台任务:把线程 CPU 优先级降到 BELOW_NORMAL,避免与雷达处理线程争抢 CPU;
//I/O 保持正常优先级(否则写入积压会拉长 Shutdown 排空时间)。队列有界,写线程变慢时
//由丢弃策略兜底,不影响处理线程。
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
#endif
try
{
WriterLoopBody(st);
}
catch (...)
{
//写线程内任何异常都不能抛出 DLL 边界;记录失败即降级
st.failed.store(true);
RDP_LOG_ERROR("[rdp_record] writer thread exception, recording stopped");
DropQueued(st);
}
}
//============================== 生命周期辅助 ==============================
Record_State::~Record_State()
{
{
std::lock_guard<std::mutex> lock(q_mtx);
stopping = true;
}
q_cv.notify_all();
if (writer.joinable())
{
writer.join();
}
std::lock_guard<std::mutex> lock(db_mtx);
try
{
db.disconnect();
}
catch (...)
{
}
}
}
namespace rdp_record
@@ -104,12 +662,22 @@ namespace rdp_record
std::unique_ptr<Record_State>& sp = State();
if (sp.get() != 0)
{
return sp->ready; //已经初始化过(含失败的幂等返回)
if (sp->ready)
{
return true; //已经初始化
}
if (sp->failed)
{
return false; //初始化失败(本次进程内不再重试)
}
sp.reset(); //上一次已 Shutdown:允许重新初始化(宿主销毁/重建工厂的场景)
}
sp.reset(new Record_State());
Record_State& st = *sp;
try
{
//创建记录目录(目录已存在时 _mkdir 返回 EEXIST,忽略;逻辑与 rdp_log.cpp 一致)
std::string dir = RDP_RECORD_DB_DIR;
while (dir.size() > 0 && (dir[dir.size() - 1] == '\\' || dir[dir.size() - 1] == '/'))
@@ -125,7 +693,7 @@ namespace rdp_record
char stamp[32] = {0};
if (!TimeStamp(stamp, sizeof(stamp)))
{
RDP_LOG << "[rdp_record] cannot get local time, recording disabled" << std::endl;
RDP_LOG_WARN("[rdp_record] cannot get local time, recording disabled");
st.failed = true;
return false;
}
@@ -134,35 +702,102 @@ namespace rdp_record
st.path += stamp;
st.path += ".db";
//同一秒内重复 Init(Destroy→GetB 重连)或同目录多进程启动时,避免复用同一个库文件
if (_access(st.path.c_str(), 0) == 0)
{
const std::string base = st.path.substr(0, st.path.size() - 3); //去掉 ".db"
for (int suffix = 1; suffix < 1000; ++suffix)
{
const std::string candidate = base + "_" + std::to_string(suffix) + ".db";
if (_access(candidate.c_str(), 0) != 0)
{
st.path = candidate;
break;
}
}
}
if (!st.db.connect(st.path))
{
RDP_LOG << "[rdp_record] cannot open database: " << st.path << std::endl;
RDP_LOG_WARN("[rdp_record] cannot open database: {}", st.path);
st.failed = true;
return false;
}
//调试用途的性能取舍:关闭同步刷盘(提交时不再 fsync),崩溃时可能丢失尾部数据;
//如需崩溃安全可改为 PRAGMA synchronous=NORMAL(或在 sqlite3 层自行开启 WAL)。
//注意:ormpp 的 execute() 只接受「不返回结果行」的语句,故不在此设置 journal_mode。
st.db.execute("PRAGMA synchronous=OFF;");
ApplyPragmas(st);
//启动记录写线程(处理线程之后只入队,不直接访问数据库)
st.writer = std::thread(WriterLoop, std::ref(st));
st.ready = true;
RDP_LOG << "[rdp_record] enabled, database: " << st.path << std::endl;
RDP_LOG_INFO("[rdp_record] enabled, database: {} (async writer, write_batch_rows={}, queue_cap_bytes={})",
st.path, RDP_RECORD_WRITE_BATCH_ROWS, RDP_RECORD_QUEUE_CAP_BYTES);
return true;
}
catch (const std::exception& e)
{
RDP_LOG_ERROR("[rdp_record] init exception: {}", e.what());
}
catch (...)
{
RDP_LOG_ERROR("[rdp_record] init exception");
}
st.failed = true;
st.ready = false;
try
{
st.db.disconnect();
}
catch (...)
{
}
return false;
}
void Shutdown()
{
std::unique_ptr<Record_State>& sp = State();
if (sp.get() == 0 || !sp->ready)
if (sp.get() == 0)
{
return;
}
std::lock_guard<std::mutex> lock(sp->mtx);
RDP_LOG << "[rdp_record] shutdown, database: " << sp->path << std::endl;
sp->db.disconnect();
sp->ready = false; //path 保留,便于宿主在关库后仍能取到本次记录库路径
Record_State& st = *sp;
if (!st.ready)
{
sp.reset();
return;
}
//1) 停止接收新数据;2) 等待写线程排空队列/pending 并退出;3) 检查点/关库;4) 打印统计
{
std::lock_guard<std::mutex> lock(st.q_mtx);
st.stopping = true;
}
st.q_cv.notify_all();
if (st.writer.joinable())
{
st.writer.join();
}
LogStats(st, "shutdown");
try
{
std::lock_guard<std::mutex> lock(st.db_mtx);
//WAL 模式下把 -wal 合并回主库(尽力而为;失败不影响关库)
st.db.query_s<std::tuple<int, int, int>>("PRAGMA wal_checkpoint(TRUNCATE);");
st.db.disconnect();
}
catch (...)
{
}
RDP_LOG_INFO("[rdp_record] shutdown, database: {}", st.path);
st.ready = false;
//保留状态对象(连接已断开、写线程已退出):下次 Init() 检测到 !ready && !failed 时复位并重新建库。
//不在 Shutdown 内直接释放,避免宿主在 Destroy 的同时仍有记录调用时出现悬空指针(防御性处理)。
}
std::string db_path()
@@ -171,28 +806,49 @@ namespace rdp_record
return (sp.get() == 0) ? std::string() : sp->path;
}
Stats stats()
{
Stats s;
std::unique_ptr<Record_State>& sp = State();
if (sp.get() == 0)
{
return s;
}
s.enqueued_rows = sp->counters.enqueued_rows.load();
s.dropped_rows = sp->counters.dropped_rows.load();
s.written_rows = sp->counters.written_rows.load();
s.transactions = sp->counters.transactions.load();
s.write_errors = sp->counters.write_errors.load();
s.queue_peak_bytes = sp->counters.queue_peak_bytes.load();
s.queue_peak_rows = sp->counters.queue_peak_rows.load();
return s;
}
//============================== 遗留同步接口(实体) ==============================
//说明:会阻塞调用线程,常规记录点请使用上面各表的异步入队接口;仅保留给测试/特殊场景。
template <typename T>
void Write(const T& v)
{
std::unique_ptr<Record_State>& sp = State();
if (sp.get() == 0 || !sp->ready || sp->failed)
if (sp.get() == 0 || !sp->ready || sp->failed.load())
{
return; //未初始化 / 初始化失败 / 已关库:静默忽略
}
std::lock_guard<std::mutex> lock(sp->mtx);
std::lock_guard<std::mutex> lock(sp->db_mtx);
if (!PrepareWrite<T>(*sp))
{
return;
}
//写入失败时每个实体只提示一次:不重试、不抛出、不影响雷达数据处理
static bool reported = false;
if (sp->db.insert(v) == INT_MIN && !reported)
if (sp->db.insert(v) == INT_MIN)
{
reported = true;
ReportError(*sp, "insert");
sp->counters.write_errors.fetch_add(1);
sp->tables[TableName<T>()] = 2;
ReportError(*sp, "insert", TableName<T>());
return;
}
sp->counters.written_rows.fetch_add(1);
sp->counters.transactions.fetch_add(1);
}
template <typename T>
@@ -204,24 +860,84 @@ namespace rdp_record
}
std::unique_ptr<Record_State>& sp = State();
if (sp.get() == 0 || !sp->ready || sp->failed)
if (sp.get() == 0 || !sp->ready || sp->failed.load())
{
return;
}
InsertEntities<T>(*sp, v);
}
std::lock_guard<std::mutex> lock(sp->mtx);
if (!PrepareWrite<T>(*sp))
//============================== 异步入队接口(源结构体) ==============================
void WritePoints(std::vector<PointRecv> v)
{
if (v.empty())
{
return;
}
//写入失败时每个实体只提示一次:不重试、不抛出、不影响雷达数据处理
static bool reported = false;
if (sp->db.insert(v) == INT_MIN && !reported)
{
reported = true;
ReportError(*sp, "insert");
Batch b;
b.kind = BatchKind::Point;
b.points = std::move(v);
EnqueueBatch(std::move(b));
}
void WriteCohesionTWS(std::vector<PointRecv> v)
{
if (v.empty())
{
return;
}
Batch b;
b.kind = BatchKind::CohesionTws;
b.points = std::move(v);
EnqueueBatch(std::move(b));
}
void WriteCohesionTAS(std::vector<PointRecv> v)
{
if (v.empty())
{
return;
}
Batch b;
b.kind = BatchKind::CohesionTas;
b.points = std::move(v);
EnqueueBatch(std::move(b));
}
void WriteTempTracks(std::vector<Temp_track> v)
{
if (v.empty())
{
return;
}
Batch b;
b.kind = BatchKind::TempTrack;
b.temp_tracks = std::move(v);
EnqueueBatch(std::move(b));
}
void WriteTrustTracks(std::vector<Trust_Track> v)
{
if (v.empty())
{
return;
}
Batch b;
b.kind = BatchKind::TrustTrack;
b.trust_tracks = std::move(v);
EnqueueBatch(std::move(b));
}
void WriteAssoInfos(std::vector<Asso_Info> v)
{
if (v.empty())
{
return;
}
Batch b;
b.kind = BatchKind::AssoInfo;
b.asso_infos = std::move(v);
EnqueueBatch(std::move(b));
}
}
@@ -246,11 +962,11 @@ YLT_REFL(RdpRecPointCohesionTAS, id,point_id,frame_id,circle_id,range,azimuth, v
RDP_RECORD_DEFINE(RdpRecPointCohesionTAS);
//临时航迹
YLT_REFL(RdpRecTempTrack, id, temp_track_id,point_id,circle_id,X,P,r,azi,vr,height,SNR,RCS,amplitude, CPI_time,GNSS_time,d,buff_round,temp_track_section_idx,asso_flag,pitch_num);
YLT_REFL(RdpRecTempTrack, id, temp_track_id,point_id,circle_id,X,P,r,azi,vr,height,SNR,RCS,amplitude, quality, CPI_time,GNSS_time,d,buff_round,temp_track_section_idx,asso_flag,pitch_num);
RDP_RECORD_DEFINE(RdpRecTempTrack);
//可靠航迹
YLT_REFL(RdpRecTrustTrack, id,track_id,point_id,circle_id,Track_Index,Range,Azimuth,Elevation,Velocity,Amplitude,Height,T_track,GNSS_time,Track_Sum,Point_Index,Track_Mode,
YLT_REFL(RdpRecTrustTrack, id,track_id,point_id,circle_id,Track_Index,Range,Azimuth,Elevation,Velocity,Amplitude,Height,Quality,T_track,GNSS_time,Track_Sum,Unupdated_time,Point_Index,Point_sum,Valid_point_sum,Track_Mode,
Track_Update_Flag,Extrapolate_round,point_flag,approach_flag,manual_delete_flag,manual_tracking_flag,
associate_point_number,primary_target_flag,Target_Type,Track_section_idx,
range_point,azi_point,elev_point,vr_point,prf_point,point_type,snr_point,
@@ -260,7 +976,7 @@ RDP_RECORD_DEFINE(RdpRecTrustTrack);
//关联信息
YLT_REFL(RdpRecAssoInfo, id,point_id,track_id,circle_id,d1,d2,d3,d_min,point_index,track_index,
delta_T,delta_h,R,VT,
thd_d,thd_h,thd_r,
tas_flag,thd_d,thd_h,thd_r,
min_d_flag);
RDP_RECORD_DEFINE(RdpRecAssoInfo);
+65 -18
View File
@@ -5,42 +5,88 @@
// 功能(完整说明见 docs/data_record_debug.md):
// 1) 把结构体数据按「一个记录实体一张表」的方式写入 SQLite 数据库,结构体到数据表的映射由
// 内置的 ormpp 完成(third_party/ormpp,见该目录 UPSTREAM.md);
// 2) 整个功能由构建开关控制:CMake 选项 RDP_DATA_RECORD(AUTO/ON/OFF)开启时由构建系统定义
// 2) 性能模型(V1.5.8 优化后):
// - 记录点只做「收集源结构体 → 一次入队」,在雷达处理线程上只有拷贝 + 队列 push 的开销;
// - 实体构造(X/P 等数组转文本)与 SQLite 写入由独立的写线程完成,批量事务提交;
// - 队列有界,写线程跟不上时整批丢弃并计数,**绝不阻塞雷达处理线程**;
// 3) 整个功能由构建开关控制:CMake 选项 RDP_DATA_RECORD(AUTO/ON/OFF)开启时由构建系统定义
// RDP_RECORD_ENABLE,rdp_record.cpp 与 sqlite3 amalgamation 才参与编译;关闭(交付/Release
// 构建默认)时本模块完全不编译,RDP_RECORD / RDP_RECORD_BATCH 展开为 ((void)0)、实参不求值,
// 不留任何代码与运行开销;
// 3) 生命周期:Data_Process_Factory::GetB() 调用 rdp_record::Init()(建目录、开库),
// Data_Process_Factory::Destroy() 调用 rdp_record::Shutdown()(关库);Init 之前、初始化
// 失败之后或 Shutdown 之后的写入一律静默忽略,不影响雷达数据处理;
// 4) 记录点用法(新增数据 = 加结构体 + 注册 + 一行记录点,配方见 docs/data_record_debug.md):
// 单条:RDP_RECORD(RdpRecPointTws{0, 值1, 值2, ...});
// 批量:RDP_RECORD_BATCH(recs); // recs 为 std::vector<某记录实体>
// 构建默认)时本模块完全不编译,所有记录宏展开为 ((void)0)、实参不求值,不留任何开销;
// 4) 生命周期:Data_Process_Factory::GetB() 调用 rdp_record::Init()(建目录、开库、启动写线程),
// Data_Process_Factory::Destroy() 调用 rdp_record::Shutdown()(停止入队、排空队列、关库);
// Init 之前、初始化失败之后或 Shutdown 之后的写入一律静默忽略,不影响雷达数据处理;
// 5) 记录点用法(新增数据 = 加记录实体 + 注册 + 一行批量记录点,配方见 docs/data_record_debug.md):
// 帧/圈内收集:std::vector<源结构体> recs;
// 记录点: RDP_RECORD_POINTS(recs); // 按表选择对应宏,内部 std::move
// 需要语句/局部变量的记录点请整体用 #ifdef RDP_RECORD_ENABLE ... #endif 包裹,
// 保证关闭记录功能时既无代码也无「变量未使用」告警;
// 5) 记录实体类型必须先在 rdp_record_entity.h 中声明并注册,否则链接期报 LNK2019
// 6) 记录实体类型必须先在 rdp_record_entity.h 中声明并注册,否则链接期报 LNK2019
// (extern template 已阻止 C++17 调用方隐式实例化)。
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
//记录源结构体(定义在 struct.h;此处只做声明,避免业务头文件依赖 struct.h 的全部内容)
struct PointRecv;
struct Temp_track;
struct Trust_Track;
struct Asso_Info;
namespace rdp_record
{
bool Init(); //初始化记录库(由 Data_Process_Factory::GetB() 调用)
void Shutdown(); //关闭记录库(由 Data_Process_Factory::Destroy() 调用)
std::string db_path(); //当前记录库文件全路径(未启用或未成功打开时返回空串)
//记录统计(仅用于调试/验收;Shutdown 时自动打印,也可随时读取)
struct Stats
{
std::uint64_t enqueued_rows = 0; //已入队行数
std::uint64_t dropped_rows = 0; //队列满/停止后丢弃的行数
std::uint64_t written_rows = 0; //已成功写入数据库的行数
std::uint64_t transactions = 0; //提交事务数
std::uint64_t write_errors = 0; //写入失败次数
std::uint64_t queue_peak_bytes = 0; //队列峰值字节数
std::uint64_t queue_peak_rows = 0; //队列峰值行数
};
//写入单条记录实体(T 为记录实体类型,须在 rdp_record.cpp 中显式实例化)
bool Init(); //初始化记录库并启动写线程(由 Data_Process_Factory::GetB() 调用)
void Shutdown(); //停止写线程、排空队列并关闭记录库(由 Data_Process_Factory::Destroy() 调用)
std::string db_path(); //当前记录库文件全路径(未启用或未成功打开时返回空串)
Stats stats(); //当前统计快照(任何时刻可调用,线程安全)
//异步入队接口(处理线程只拷贝源结构体并送入有界队列;参数按值接收,调用点用 std::move)
//仅在记录功能开启(RDP_RECORD_ENABLE)时有实现;关闭构建由对应宏展开为空。
void WritePoints(std::vector<PointRecv> v); //RdpRecPoint(输入点迹,TWS/TAS 共用)
void WriteCohesionTWS(std::vector<PointRecv> v); //RdpRecPointCohesionTWS
void WriteCohesionTAS(std::vector<PointRecv> v); //RdpRecPointCohesionTAS
void WriteTempTracks(std::vector<Temp_track> v); //RdpRecTempTrack(临时航迹)
void WriteTrustTracks(std::vector<Trust_Track> v); //RdpRecTrustTrack(可靠航迹)
void WriteAssoInfos(std::vector<Asso_Info> v); //RdpRecAssoInfo(点航关联信息)
//遗留接口:同步写入记录实体(在调用线程内直接写库,会阻塞;仅供测试/特殊场景使用)。
//T 为记录实体类型,须在 rdp_record.cpp 中显式实例化。
template <typename T> void Write(const T& v);
//批量写入同一记录实体的多条记录(内部使用事务)
template <typename T> void Write(const std::vector<T>& v);
}
#ifdef RDP_RECORD_ENABLE
//记录宏:把指定记录实体写入数据库(首次写入该实体时自动建表)
//按记录表批量入队(参数为源结构体 vector,宏内部 std::move;调用后 vector 为空)
#define RDP_RECORD_POINTS(v) ::rdp_record::WritePoints(std::move(v))
#define RDP_RECORD_COHESION_TWS(v) ::rdp_record::WriteCohesionTWS(std::move(v))
#define RDP_RECORD_COHESION_TAS(v) ::rdp_record::WriteCohesionTAS(std::move(v))
#define RDP_RECORD_TEMP_TRACKS(v) ::rdp_record::WriteTempTracks(std::move(v))
#define RDP_RECORD_TRUST_TRACKS(v) ::rdp_record::WriteTrustTracks(std::move(v))
#define RDP_RECORD_ASSO_INFOS(v) ::rdp_record::WriteAssoInfos(std::move(v))
//遗留宏:记录实体单条/批量同步写(会阻塞调用线程;新记录点请使用上面的批量入队接口)
#define RDP_RECORD(...) ::rdp_record::Write(__VA_ARGS__)
//批量记录宏:把 std::vector<记录实体> 一次性写入(每批一个事务)
#define RDP_RECORD_BATCH(...) ::rdp_record::Write(__VA_ARGS__)
#else
//记录功能关闭:记录模块不参与编译,宏展开为空(实参不求值)
//记录功能关闭:记录模块不参与编译,所有宏展开为空(实参不求值)
#define RDP_RECORD_POINTS(v) ((void)0)
#define RDP_RECORD_COHESION_TWS(v) ((void)0)
#define RDP_RECORD_COHESION_TAS(v) ((void)0)
#define RDP_RECORD_TEMP_TRACKS(v) ((void)0)
#define RDP_RECORD_TRUST_TRACKS(v) ((void)0)
#define RDP_RECORD_ASSO_INFOS(v) ((void)0)
#define RDP_RECORD(...) ((void)0)
#define RDP_RECORD_BATCH(...) ((void)0)
namespace rdp_record
@@ -48,6 +94,7 @@ namespace rdp_record
inline bool Init() { return false; }
inline void Shutdown() {}
inline std::string db_path() { return std::string(); }
inline Stats stats() { return Stats(); }
template <typename T> inline void Write(const T&) {}
template <typename T> inline void Write(const std::vector<T>&) {}
}
+14 -1
View File
@@ -225,7 +225,7 @@ struct RdpRecPointCohesionTAS
};
RDP_RECORD_DECLARE(RdpRecPointCohesionTAS);
// 零时航迹
// 临时航迹
struct RdpRecTempTrack
{
int64_t id; //自增主键(写入时固定填 0)
@@ -243,6 +243,7 @@ struct RdpRecTempTrack
double SNR; //信噪比
double RCS; //RCS
double amplitude; //幅度
double quality; //航迹质量
long long CPI_time; //时间戳
long long GNSS_time; //GNSS时间戳
double d; //关联上的点的d(马氏距离)
@@ -269,6 +270,7 @@ struct RdpRecTempTrack
RCS = p.RCS;
SNR = p.SNR;
amplitude = p.amplitude;
quality = p.quality;
CPI_time = p.CPI_time;
GNSS_time = p.GNSS_time;
d = p.d;
@@ -294,10 +296,14 @@ struct RdpRecTrustTrack
double Velocity; //航向速度
double Amplitude; //幅度
double Height; //高度
double Quality; //航迹质量
long long T_track; //航迹时间
long long GNSS_time; //GNSS时间
long long Unupdated_time; //航迹未更新时间
int Track_Sum; //航迹总数
int Point_Index; //该航迹上的第几个点
int Point_sum; //本条航迹更新点的数目
int Valid_point_sum; //本条航迹有效点的数目
int Track_Mode; //跟踪模式
int Track_Update_Flag; //航迹更新标志 1已更新/0未更新
int Extrapolate_round; //航迹持续外推时间
@@ -347,10 +353,14 @@ struct RdpRecTrustTrack
Velocity = p.Velocity;
Amplitude = p.Amplitude;
Height = p.Height;
Quality = p.Quality;
T_track = p.T_track;
GNSS_time = p.GNSS_time;
Track_Sum = p.Track_Sum;
Unupdated_time = p.Unupdated_time;
Point_Index = p.Point_Index;
Point_sum = p.Point_sum;
Valid_point_sum = p.Valid_point_sum;
Track_Mode = p.Track_Mode;
Track_Update_Flag = p.Track_Update_Flag;
Extrapolate_round = p.Extrapolate_round;
@@ -407,6 +417,8 @@ struct RdpRecAssoInfo
double R;
double VT;
bool tas_flag; //TAS关联标志 1是 0否
bool thd_d;
bool thd_h;
bool thd_r;
@@ -432,6 +444,7 @@ struct RdpRecAssoInfo
delta_h = p.delta_h;
R = p.R;
VT = p.VT;
tas_flag = p.tas_flag;
thd_d = p.thd_d;
thd_h = p.thd_h;
thd_r = p.thd_r;
+106 -5
View File
@@ -44,6 +44,7 @@ struct Trust_Track
long long circle_id; //处理圈数ID
int Track_Index; //航迹号
int Track_fingerprint; //航迹指纹
double Range; //距离
double Azimuth; //方位
@@ -51,12 +52,18 @@ struct Trust_Track
double Velocity; //航向速度
double Amplitude; //幅度
double Height; //高度
double RCS; //RCS
double Quality; //航迹质量
long long T_track; //航迹时间
long long T_track; //航迹时间(CPI时间)
long long GNSS_time; //GNSS时间
long long Unupdated_time; //航迹未更新时间
int Track_Sum; //航迹总数
int Point_Index; //该航迹上的第几个点
int Point_sum; //本条航迹更新点的数目
int Valid_point_sum; //本条航迹有效点的数目
int associate_point_number; //关联的点数
int Track_Mode; //跟踪模式
int Track_Update_Flag; //航迹更新标志 1已更新/0未更新
@@ -67,8 +74,6 @@ struct Trust_Track
int manual_delete_flag; //手动航迹删除
int manual_tracking_flag; //手动TAS
int associate_point_number; //关联的点数
int primary_target_flag; //重点目标标记
int Target_Type; //目标类型 搜索目标 低空目标 地面目标
@@ -87,8 +92,6 @@ struct Trust_Track
float amp_asv_point; //关联上的点迹信息(归一化幅度)
float prn_fftn_point; //点迹速度维FFT点数
double RCS;
int pitch_num; // 俯仰波位号
float speed_dim[129]; // 目标的十字星数据,目标点速度维129个点
float range_dim[8]; // 目标的十字星数据,目标点距离维8个点
@@ -122,6 +125,7 @@ struct Temp_track
double SNR; //信噪比
double RCS; //RCS
double amplitude; //幅度
double quality; //航迹质量
long long CPI_time; //时间戳
long long GNSS_time; //GNSS时间戳
@@ -157,6 +161,8 @@ struct Asso_Info
double R;
double VT;
bool tas_flag; //TAS关联标志 1是 0否
bool thd_d;
bool thd_h;
bool thd_r;
@@ -184,4 +190,99 @@ struct Direct_Tracking_Target
};
// 系统工作参数结构体
struct RDP_parameter
{
// ******************* 雷达工作参数 ************************//
int radar_work_mode; //工作模式
int scan_mode; //扫描模式 0=相扫模式 1=机扫模式
float freq_0_GHz; //频点0频率
float radar_height; //雷达平台高度 (m)
float radar_system_delay; //系统延迟 (s)
float data_rate_tws; //TWS模式数据率
float data_rate_tas; //TAS模式数据率
float data_rate_track_only; //纯跟踪模式数据率
// ******************* 点迹凝聚参数 ************************//
float point_cohesion_range; //距离凝聚范围
float point_cohesion_velocity; //速度凝聚范围
float point_cohesion_azimuth; //方位凝聚范围
float point_cohesion_range_min; //最小凝聚距离
float point_cohesion_range_max; //最大凝聚距离
// ******************* 航迹关联参数 ************************//
float sigma_range; //距离量测精度
float sigma_azimuth; //方位量测精度
float sigma_pitch; //俯仰量测精度
float sigma_velocity; //速度量测精度
float associate_threshold; //关联门限
int extrapolate_max; //最大外推点数
int extrapolate_max_tas; //TAS最大外推点数
int height_smooth_win; //高度平滑窗长
// ******************* 航迹起始参数 ************************//
float track_init_threshold; //起航门限
float track_init_angle; //起航夹角
int track_init_point_num_min; //最少起批点数
float track_init_quality_L3; //3点起批质量门限
float track_init_quality_L4; //4点起批质量门限
float track_init_quality_L5; //5点起批质量门限
float track_init_quality_L6; //6点起批质量门限
float track_init_quality_L7; //7点起批质量门限
float track_init_velocity_min; //航迹起始最小速度
float track_init_velocity_max; //航迹起始最大速度
// ******************* 航迹消亡参数 ************************//
int track_die_round; //航迹消亡圈数
int track_die_round_pramary; //重点航迹消亡圈数
int track_die_round_tas; //TAS航迹消亡圈数
float track_die_quality; //航迹消批质量条件
float track_die_quality_pramary; //重点航迹消批质量条件
// ******************* 航迹质量管理参数 ************************//
float track_quality_max; //航迹质量最大值
float track_quality_min; //航迹质量最小值
float track_quality_d_weight; //马氏距离权重
float track_quality_snr_weight; //信噪比权重
float track_quality_len_weight; //航迹长度权重
float track_quality_iter_coeff; //航迹质量迭代系数
float track_quality_decrease_coeff_o2; //航迹质量下降系数2次项
float track_quality_decrease_coeff_o1; //航迹质量下降系数1次项
// ******************* 航迹批号管理 ************************//
int track_index_max; //最大航迹批号
// ******************* 波束调度参数 ************************//
int default_track_beam_type; //默认跟踪波束类型
int tas_track_num_max; //最大同时跟踪目标数
// ******************* singer模型机动参数 ************************//
float model_1_Q_fast; //模型1
float model_1_Q_slow; //模型1
float model_2_Q_fast; //模型2
float model_2_Q_slow; //模型2
float model_3_Q_fast; //模型3
float model_3_Q_slow; //模型3
// ******************* 航迹起始屏蔽区 ************************//
int track_init_prohibite_area_num; //航迹起始屏蔽区数
float track_init_prohibite_area_range_min[30]; //最小距离 (m)
float track_init_prohibite_area_range_max[30]; //最大距离 (m)
float track_init_prohibite_area_azimuth_min[30]; //最小方位角 (度)
float track_init_prohibite_area_azimuth_max[30]; //最大方位角 (度)
// ******************* 禁止TAS跟踪区 ************************//
int tas_prohibite_area_num; //禁止TAS的区域数
float tas_prohibite_area_range_min[30]; //最小距离 (m)
float tas_prohibite_area_range_max[30]; //最大距离 (m)
float tas_prohibite_area_azimuth_min[30]; //最小方位角 (度)
float tas_prohibite_area_azimuth_max[30]; //最大方位角 (度)
};
// ******************* 全局工作参数对象 ************************//
// 所有处理模块直接访问该全局对象,不再通过函数参数逐层传递。
// 定义在 data_process.cpp;由 Data_Process::parameters_reload() 在配置文件完整校验通过后整体赋值
// (失败时保持原值)。处理期间只读;配置重载应与处理互斥,宿主按单线程接口使用(见 README BUG-03)。
extern RDP_parameter rdp_parameter;
#endif // STRUCT_H
+101 -85
View File
@@ -9,15 +9,9 @@
using namespace std;
// parameters.h 中定义了 DATA_RATE_TAS 宏(相扫0.3/机扫0.0625),
// 而本模块的波束门控必须使用 RadarPara 结构体成员 Work_Parameter.DATA_RATE_TAS
// (由dll调用者经 track_process_parameters_initial 输入),故取消该宏定义,
// 否则 Work_Parameter.DATA_RATE_TAS 会被预处理器展开为非法常量表达式。
#undef DATA_RATE_TAS
TAS_Ctrl::TAS_Ctrl()
{
memset(tas_target_queue,0,TAS_QUEUE_LENGTH*sizeof(Tracking_Target));
memset(tas_target_queue,0,RDP_TAS_QUEUE_CAPACITY*sizeof(Tracking_Target));
tas_target_num=0;
}
@@ -33,19 +27,18 @@ void TAS_Ctrl::tas_ctrl_process(std::vector<Trust_Track> *trust_track
struct TrackingBeam *Tracking_beam,
struct Track Trust_Track_Output[][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter,
long long latest_timestamp)
{
if (Trust_track_num_Output == 0) return;
*Trust_track_num_Output = 0;
tas_target_add(trust_track, Trust_Track_Output, Trust_track_num_Output,Work_Parameter);
tas_target_del(trust_track, Trust_Track_Output, Trust_track_num_Output,Work_Parameter);
tas_beam_output(trust_track,Tracking_beam,Work_Parameter,latest_timestamp);
tas_target_add(trust_track, Trust_Track_Output, Trust_track_num_Output);
tas_target_del(trust_track, Trust_Track_Output, Trust_track_num_Output);
tas_beam_output(trust_track,Tracking_beam,latest_timestamp);
// //跟踪队列移位
// struct Tracking_Target tas_target_tmp;
// memcpy(&tas_target_tmp, &tas_target_queue[TAS_QUEUE_LENGTH-1], sizeof(Tracking_Target));
// for (int i=TAS_QUEUE_LENGTH-1;i>0;i--)
// memcpy(&tas_target_tmp, &tas_target_queue[RDP_TAS_QUEUE_CAPACITY-1], sizeof(Tracking_Target));
// for (int i=RDP_TAS_QUEUE_CAPACITY-1;i>0;i--)
// {
// memcpy(&tas_target_queue[i],&tas_target_queue[i-1],sizeof(Tracking_Target));
// }
@@ -54,27 +47,26 @@ void TAS_Ctrl::tas_ctrl_process(std::vector<Trust_Track> *trust_track
void TAS_Ctrl::tas_target_add(std::vector<Trust_Track> *trust_track,
struct Track Trust_Track_Output[][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter)
int *Trust_track_num_Output)
{
for (size_t i=0;i<trust_track->size();i++)
{
double r=sqrt(pow((*trust_track)[i].X[0],2)+pow((*trust_track)[i].X[3],2));
double v=sqrt(pow((*trust_track)[i].X[1],2)+pow((*trust_track)[i].X[4],2));
double h=(*trust_track)[i].Height+Work_Parameter.Height;
double h=(*trust_track)[i].Height+rdp_parameter.radar_height;
double azi = atan2((*trust_track)[i].X[3],(*trust_track)[i].X[0]);
if(azi<0)
azi=azi+2*PI;
azi=azi/PI*180;
//进入跟踪的条件: 1.手动跟踪的目标 或 满足速度、距离、高度条件满足 关联点数大于2 2.不在禁止跟踪区域
if( ( (*trust_track)[i].manual_tracking_flag == 1)//|| (tas_auto_start( v, r, h, Work_Parameter)==1 && (*trust_track)[i][k].associate_point_number>=2)
&& tas_target_num < MAX_TAS_NUM
if( ( (*trust_track)[i].manual_tracking_flag == 1)//|| (tas_auto_start( v, r, h)==1 && (*trust_track)[i][k].associate_point_number>=2)
&& tas_target_num < rdp_parameter.tas_track_num_max
&& (*trust_track)[i].Track_Mode == 0
&& tas_prohibite_area( r, azi, v, h, Work_Parameter) == 0)
&& tas_prohibite_area( r, azi, v, h) == 0)
{
for(int j=0;j<TAS_QUEUE_LENGTH;j++)
for(int j=0;j<RDP_TAS_QUEUE_CAPACITY;j++)
{
if(tas_target_queue[j].empty_flag==0)// && j%2==0)
{
@@ -94,22 +86,80 @@ void TAS_Ctrl::tas_target_add(std::vector<Trust_Track> *trust_track,
//目标跟踪状态发生改变 对外输出航迹更新信息
*Trust_track_num_Output= *Trust_track_num_Output+1;
double r_output,azmi_output;
coor_trans Coor_trans;
Coor_trans.cart2polar((*trust_track)[i].X[0],(*trust_track)[i].X[3],&r_output,&azmi_output);
Trust_Track_Output[*Trust_track_num_Output-1][0].Point_Sum=1;
Trust_Track_Output[*Trust_track_num_Output-1][0].Range=static_cast<float>(r_output);
Trust_Track_Output[*Trust_track_num_Output-1][0].Azimuth=static_cast<float>(azmi_output/PI*180);
Trust_Track_Output[*Trust_track_num_Output-1][0].Track_Index=(*trust_track)[i].Track_Index; //航迹号
Trust_Track_Output[*Trust_track_num_Output-1][0].Range_V=
static_cast<float>(sqrt((*trust_track)[i].X[1]*(*trust_track)[i].X[1]+(*trust_track)[i].X[4]*(*trust_track)[i].X[4]));
double x,y;
x=(*trust_track)[i].X[0]+(*trust_track)[i].X[1]*rdp_parameter.radar_system_delay;
y=(*trust_track)[i].X[3]+(*trust_track)[i].X[4]*rdp_parameter.radar_system_delay;
coor_trans Coor_trans;
double r, azi;
Coor_trans.cart2polar(x, y, &r, &azi);
Trust_Track_Output[*Trust_track_num_Output-1][0].Range=static_cast<float>(r);
Trust_Track_Output[*Trust_track_num_Output-1][0].Azimuth=static_cast<float>(azi/PI*180);
if((*trust_track)[i].Height<r)
Trust_Track_Output[*Trust_track_num_Output-1][0].Elevation = static_cast<float>(asin((*trust_track)[i].Height/r)/PI*180);
else
Trust_Track_Output[*Trust_track_num_Output-1][0].Elevation = static_cast<float>((*trust_track)[i].elev_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].Track_Index=(*trust_track)[i].Track_Index;
Trust_Track_Output[*Trust_track_num_Output-1][0].Range_V = static_cast<float>(sqrt((*trust_track)[i].X[1]*(*trust_track)[i].X[1]+(*trust_track)[i].X[4]*(*trust_track)[i].X[4]));
Trust_Track_Output[*Trust_track_num_Output-1][0].Amplitude = static_cast<float>((*trust_track)[i].Amplitude);
Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Point=(*trust_track)[i].point_flag;
Trust_Track_Output[*Trust_track_num_Output-1][0].Track_Mode=(*trust_track)[i].Track_Mode;
Trust_Track_Output[*Trust_track_num_Output-1][0].Elevation=static_cast<float>(asin((*trust_track)[i].Height/r_output)/PI*180);
Trust_Track_Output[*Trust_track_num_Output-1][0].z=static_cast<float>((*trust_track)[i].Height+Work_Parameter.Height);
Trust_Track_Output[*Trust_track_num_Output-1][0].Target_Type=(*trust_track)[i].Target_Type;
Trust_Track_Output[*Trust_track_num_Output-1][0].track_time=static_cast<float>((*trust_track)[i].T_track/1000.0);
Trust_Track_Output[*Trust_track_num_Output-1][0].GNSS_time=(*trust_track)[i].GNSS_time;
Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Point = (*trust_track)[i].point_flag;
Trust_Track_Output[*Trust_track_num_Output-1][0].z=static_cast<float>((*trust_track)[i].Height+rdp_parameter.radar_height);
Trust_Track_Output[*Trust_track_num_Output-1][0].Quality = static_cast<float>((*trust_track)[i].Quality * rdp_parameter.track_quality_max);
double Direction_Angle;
Direction_Angle=atan2((*trust_track)[i].X[4], (*trust_track)[i].X[1]);
if(Direction_Angle<0){Direction_Angle=Direction_Angle+2*PI;}
Trust_Track_Output[*Trust_track_num_Output-1][0].Direction_Angle=static_cast<float>(Direction_Angle/PI*180);
//关联点信息
Trust_Track_Output[*Trust_track_num_Output-1][0].range_point=static_cast<float>((*trust_track)[i].range_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].azi_point=static_cast<float>((*trust_track)[i].azi_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].elev_point=static_cast<float>((*trust_track)[i].elev_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].vr_point=static_cast<float>((*trust_track)[i].vr_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].point_type=0;
Trust_Track_Output[*Trust_track_num_Output-1][0].prf_point = static_cast<float>((*trust_track)[i].prf_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].track_snr = static_cast<float>((*trust_track)[i].snr_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].amp_asv_point = static_cast<float>((*trust_track)[i].amp_asv_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].prn_fftn_point = static_cast<float>((*trust_track)[i].prn_fftn_point);
//输出平滑后的高度
Trust_Track_Output[*Trust_track_num_Output-1][0].z = static_cast<float>((*trust_track)[i].Height+rdp_parameter.radar_height);
Trust_Track_Output[*Trust_track_num_Output-1][0].x=static_cast<float>((*trust_track)[i].X[0]);
Trust_Track_Output[*Trust_track_num_Output-1][0].y=static_cast<float>((*trust_track)[i].X[3]);
Trust_Track_Output[*Trust_track_num_Output-1][0].v_x=static_cast<float>((*trust_track)[i].X[1]);
Trust_Track_Output[*Trust_track_num_Output-1][0].v_y=static_cast<float>((*trust_track)[i].X[4]);
if(acos((-x * cos(Direction_Angle) - y * sin(Direction_Angle)) / r ) < PI / 2)
{
Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Direction=1;
// if(r*abs(sin(azi - Direction_Angle))<500)
// {
// Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Direction=1;
// }
// else
// {
// Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Direction=0;
// }
}
else
{
Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Direction=0;
}
Trust_Track_Output[*Trust_track_num_Output-1][0].track_rcs = static_cast<float>((*trust_track)[i].RCS);
Trust_Track_Output[*Trust_track_num_Output-1][0].pitch_num = (*trust_track)[i].pitch_num;
std::memcpy(Trust_Track_Output[*Trust_track_num_Output-1][0].speed_dim, (*trust_track)[i].speed_dim, sizeof(Trust_Track_Output[*Trust_track_num_Output-1][0].speed_dim));
std::memcpy(Trust_Track_Output[*Trust_track_num_Output-1][0].range_dim, (*trust_track)[i].range_dim, sizeof(Trust_Track_Output[*Trust_track_num_Output-1][0].range_dim));
break;
}
@@ -119,32 +169,16 @@ void TAS_Ctrl::tas_target_add(std::vector<Trust_Track> *trust_track,
};
//int TAS_Ctrl:: tas_auto_start(double v, double r, double h, struct RadarPara Work_Parameter)
//{
// 说明:TAS 自动开始(tas_auto_start)的原实现依赖改造前雷达参数结构体中的
// TAS 距离/速度/高度门限,这些参数未纳入新的 RDP_parameter,故原实现保持停用;
// 具体逻辑不再以注释代码形式保留(tas_ctrl.h 中保留声明,后续如需启用再按新参数补齐)。
//// if(r>Work_Parameter.R_TAS_min && r<Work_Parameter.R_TAS_max && v<Work_Parameter.V_TAS_max && v>Work_Parameter.V_TAS_min
//// && ( (r<=1000 && h>=Work_Parameter.TAS_Height_1) || (r<=2000 && r>1000 && h>=Work_Parameter.TAS_Height_2) || (r<=3000 && r>2000 && h>=Work_Parameter.TAS_Height_3) || (r<=4000 && r>3000 && h>=Work_Parameter.TAS_Height_4) || (r>4000 && h>=Work_Parameter.TAS_Height_5) )
//// )
// if( r>Work_Parameter.R_TAS_min && r<Work_Parameter.R_TAS_max && v<Work_Parameter.V_TAS_max && v>Work_Parameter.V_TAS_min
// && ( (r<=1000 && h>=Work_Parameter.TAS_Height_1) || (r<=2000 && r>1000 && h>=Work_Parameter.TAS_Height_2) || (r<=3000 && r>2000 && h>=Work_Parameter.TAS_Height_3) || (r<=4000 && r>3000 && h>=Work_Parameter.TAS_Height_4) || (r>4000 && h>=Work_Parameter.TAS_Height_5) ) )
// {
// return 1;
// }
// else
// {
// return 0;
// }
//// return 0;
//};
int TAS_Ctrl::tas_prohibite_area(double r,double azi, double v, double h,struct RadarPara Work_Parameter)
int TAS_Ctrl::tas_prohibite_area(double r,double azi, double v, double h)
{
for (int i=0;i<Work_Parameter.TAS_prohibite_area_num;i++)
for (int i=0;i<rdp_parameter.tas_prohibite_area_num;i++)
{
if(r<Work_Parameter.R_max_TAS_prohibited[i] && r>Work_Parameter.R_min_TAS_prohibited[i] && azi<Work_Parameter.Azimuth_max_TAS_prohibited[i] && azi>Work_Parameter.Azimuth_min_TAS_prohibited[i])
if(r<rdp_parameter.tas_prohibite_area_range_max[i] && r>rdp_parameter.tas_prohibite_area_range_min[i] && azi<rdp_parameter.tas_prohibite_area_azimuth_max[i] && azi>rdp_parameter.tas_prohibite_area_azimuth_min[i])
{
return 1;
}
@@ -156,12 +190,11 @@ int TAS_Ctrl::tas_prohibite_area(double r,double azi, double v, double h,struct
void TAS_Ctrl::tas_target_del(std::vector<Trust_Track> *trust_track,
struct Track Trust_Track_Output[][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter)
int *Trust_track_num_Output)
{
//查找TAS目标是否已经消批 若消批则移出TAS队列
for (int i=0;i<TAS_QUEUE_LENGTH;i++)
for (int i=0;i<RDP_TAS_QUEUE_CAPACITY;i++)
{
if(tas_target_queue[i].empty_flag == 1)
{
@@ -189,14 +222,14 @@ void TAS_Ctrl::tas_target_del(std::vector<Trust_Track> *trust_track,
// {
// double r=sqrt(pow((*trust_track)[i].X[0],2)+pow((*trust_track)[i].X[3],2));
// double v=sqrt(pow((*trust_track)[i].X[1],2)+pow((*trust_track)[i].X[4],2));
// double h=(*trust_track)[i].Height+Work_Parameter.Height;
// double h=(*trust_track)[i].Height+rdp_parameter.radar_height;
// if((*trust_track)[i].manual_tracking_flag == 0 && (*trust_track)[i].Track_Mode == 1 && tas_auto_end(v,r,h,Work_Parameter) == 1) //满足结束跟踪条件
// if((*trust_track)[i].manual_tracking_flag == 0 && (*trust_track)[i].Track_Mode == 1 && tas_auto_end(v,r,h) == 1) //满足结束跟踪条件
// {
// int track_index=(*trust_track)[i].Track_Index;
// for (int ii=0;ii<TAS_QUEUE_LENGTH;ii++)
// for (int ii=0;ii<RDP_TAS_QUEUE_CAPACITY;ii++)
// {
// if(tas_target_queue[ii].Index == track_index)
// {
@@ -224,7 +257,7 @@ void TAS_Ctrl::tas_target_del(std::vector<Trust_Track> *trust_track,
// Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Point=(*trust_track)[i].point_flag;
// Trust_Track_Output[*Trust_track_num_Output-1][0].Track_Mode=(*trust_track)[i].Track_Mode;
// Trust_Track_Output[*Trust_track_num_Output-1][0].Elevation=static_cast<float>(asin((*trust_track)[i].Height/r_output)/PI*180);
// Trust_Track_Output[*Trust_track_num_Output-1][0].z=static_cast<float>((*trust_track)[i].Height+Work_Parameter.Height);
// Trust_Track_Output[*Trust_track_num_Output-1][0].z=static_cast<float>((*trust_track)[i].Height+rdp_parameter.radar_height);
// Trust_Track_Output[*Trust_track_num_Output-1][0].Target_Type=(*trust_track)[i].Target_Type;
// Trust_Track_Output[*Trust_track_num_Output-1][0].track_snr = static_cast<float>((*trust_track)[i].snr_point);
// }
@@ -235,28 +268,11 @@ void TAS_Ctrl::tas_target_del(std::vector<Trust_Track> *trust_track,
};
//int TAS_Ctrl::tas_auto_end(double v, double r, double h, struct RadarPara Work_Parameter)
//{
// if((r<=1000 && h<Work_Parameter.TAS_Height_1) || (r<=2000 && r>1000 && h<Work_Parameter.TAS_Height_2) || (r<=3000 && r>2000 && h<Work_Parameter.TAS_Height_3) || (r<=4000 && r>3000 && h<Work_Parameter.TAS_Height_4) || (r>4000 && h<Work_Parameter.TAS_Height_5)
// || v<Work_Parameter.V_TAS_min || v>Work_Parameter.V_TAS_max
// )
// {
// return 1;
// }
// else
// {
// return 0;
// }
// return 0;
//};
// 说明:TAS 自动结束(tas_auto_end)的原实现同样依赖未纳入 RDP_parameter 的
// TAS 距离/速度/高度门限,故保持停用(tas_ctrl.h 中保留声明,实现待后续补齐)。
void TAS_Ctrl::tas_beam_output(std::vector<Trust_Track> *trust_track,
struct TrackingBeam *Tracking_beam,
struct RadarPara Work_Parameter,
long long latest_timestamp)
{
@@ -270,7 +286,7 @@ void TAS_Ctrl::tas_beam_output(std::vector<Trust_Track> *trust_track
double X_now[6] = {0};
//查找TAS队列中CPI时间最早的目标且满足TAS数据率的目标
for(int tas_idx=0;tas_idx<TAS_QUEUE_LENGTH;tas_idx++)
for(int tas_idx=0;tas_idx<RDP_TAS_QUEUE_CAPACITY;tas_idx++)
{
if(tas_target_queue[tas_idx].empty_flag==0)
{
@@ -285,7 +301,7 @@ void TAS_Ctrl::tas_beam_output(std::vector<Trust_Track> *trust_track
delta_T_last_track = (latest_timestamp - tas_target_queue[tas_idx].last_track_time) / 1000.0f;
//查找CPI时间最早,且满足TAS数据率的目标
if(CPI_time < earliest_CPI_time && (delta_T_last_track > Work_Parameter.DATA_RATE_TAS))
if(CPI_time < earliest_CPI_time && (delta_T_last_track > rdp_parameter.data_rate_tas))
{
earliest_CPI_time = CPI_time;
H_track = (*trust_track)[track_idx].Height;
@@ -339,13 +355,13 @@ void TAS_Ctrl::tas_beam_output(std::vector<Trust_Track> *trust_track
Tracking_beam->Elev=(float)elev;
//跟踪波束类型
Tracking_beam->type=1;
Tracking_beam->type=rdp_parameter.default_track_beam_type;
//跟踪目标批号
Tracking_beam->TAS_track_index = (*trust_track)[earliest_track_idx].Track_Index;
Tracking_beam->TAS_track_index = (*trust_track)[earliest_track_idx].Track_fingerprint;
//跟踪波束开关开启
Tracking_beam->open_flag=1;
//RDP_LOG << "TAS <" << (*trust_track)[earliest_track_idx].Track_Index << "> output: range: " <<Tracking_beam->Range << "azi: " <<Tracking_beam->Azi <<"pit: " <<Tracking_beam->Elev << std::endl;
// RDP_LOG_INFO("TAS <{}> output: range: {} azi: {} pit: {}", Tracking_beam->TAS_track_index, Tracking_beam->Range, Tracking_beam->Azi, Tracking_beam->Elev);
}
+6 -10
View File
@@ -18,7 +18,6 @@ public:
struct TrackingBeam *Tracking_beam,
struct Track Trust_Track_Output[][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter,
long long latest_timestamp);
private:
@@ -26,31 +25,28 @@ private:
//添加TAS目标
void tas_target_add(std::vector<Trust_Track> *trust_track,
struct Track Trust_Track_Output[][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter);
int *Trust_track_num_Output);
//删除TAS目标
void tas_target_del(std::vector<Trust_Track> *trust_track,
struct Track Trust_Track_Output[][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter);
int *Trust_track_num_Output);
//TAS队列信息输出
void tas_beam_output(std::vector<Trust_Track> *trust_track,
struct TrackingBeam *Tracking_beam,
struct RadarPara Work_Parameter,
long long latest_timestamp);
//TAS 自动开启条件
int tas_auto_start(double v, double r, double h, struct RadarPara Work_Parameter);
int tas_auto_start(double v, double r, double h);
//TAS 自动退出条件
int tas_auto_end(double v, double r, double h, struct RadarPara Work_Parameter);
int tas_auto_end(double v, double r, double h);
// 禁止跟踪区域判断
int tas_prohibite_area(double r,double azi, double v, double h,struct RadarPara Work_Parameter);
int tas_prohibite_area(double r,double azi, double v, double h);
struct Tracking_Target tas_target_queue[TAS_QUEUE_LENGTH]; //跟踪目标信息队列
struct Tracking_Target tas_target_queue[RDP_TAS_QUEUE_CAPACITY]; //跟踪目标信息队列
int tas_target_num; //跟踪目标数量
+115 -59
View File
@@ -8,7 +8,6 @@
#include <Eigen/Dense>
#include "rdp_record.h"
#include "rdp_record_entity.h"
using namespace Eigen;
using namespace std;
@@ -20,7 +19,7 @@ int Track_Asso:: track_asso_process(std::vector<PointRecv> *point_
std::vector <Trust_Track> *trust_track, //航迹文件
struct Track Trust_Track_Output[MAX_TRACK_NUM][10], //更新航迹信息
int *Trust_track_num_Output, //更新航迹数
struct RadarPara Work_Parameter //雷达参数
long long latest_timestamp //最新时间戳
)
{
@@ -35,7 +34,7 @@ int Track_Asso:: track_asso_process(std::vector<PointRecv> *point_
//IMM算法
model_interaction(trust_track);
model_filter(trust_track, Work_Parameter);
model_filter(trust_track, latest_timestamp);
model_output(trust_track);
@@ -67,6 +66,11 @@ int Track_Asso:: track_asso_process(std::vector<PointRecv> *point_
std::vector<PointRecv>().swap(point_process);
//输出航迹
#ifdef RDP_RECORD_ENABLE
//记录可靠航迹:先收集,输出循环结束后一次入队
std::vector<Trust_Track> rec_trust;
rec_trust.reserve(trust_track->size());
#endif
for (size_t i=0;i<trust_track->size();i++)
{ // 输出更新航迹条件:
if((*trust_track)[i].Track_Update_Flag==1){
@@ -75,8 +79,8 @@ int Track_Asso:: track_asso_process(std::vector<PointRecv> *point_
Trust_Track_Output[*Trust_track_num_Output-1][0].Point_Sum=1;
double x,y;
x=(*trust_track)[i].X[0]+(*trust_track)[i].X[1]*Work_Parameter.Sys_delay;
y=(*trust_track)[i].X[3]+(*trust_track)[i].X[4]*Work_Parameter.Sys_delay;
x=(*trust_track)[i].X[0]+(*trust_track)[i].X[1]*rdp_parameter.radar_system_delay;
y=(*trust_track)[i].X[3]+(*trust_track)[i].X[4]*rdp_parameter.radar_system_delay;
coor_trans Coor_trans;
double r, azi;
Coor_trans.cart2polar(x, y, &r, &azi);
@@ -98,7 +102,8 @@ int Track_Asso:: track_asso_process(std::vector<PointRecv> *point_
Trust_Track_Output[*Trust_track_num_Output-1][0].track_time=static_cast<float>((*trust_track)[i].T_track/1000.0);
Trust_Track_Output[*Trust_track_num_Output-1][0].GNSS_time=(*trust_track)[i].GNSS_time;
Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Point = (*trust_track)[i].point_flag;
Trust_Track_Output[*Trust_track_num_Output-1][0].z=static_cast<float>((*trust_track)[i].Height+Work_Parameter.Height);
Trust_Track_Output[*Trust_track_num_Output-1][0].z=static_cast<float>((*trust_track)[i].Height+rdp_parameter.radar_height);
Trust_Track_Output[*Trust_track_num_Output-1][0].Quality = static_cast<float>((*trust_track)[i].Quality * rdp_parameter.track_quality_max);
double Direction_Angle;
Direction_Angle=atan2((*trust_track)[i].X[4], (*trust_track)[i].X[1]);
@@ -124,7 +129,7 @@ int Track_Asso:: track_asso_process(std::vector<PointRecv> *point_
//直接输出点迹高度-20260605
//Trust_Track_Output[*Trust_track_num_Output-1][0].z = (*trust_track)[i].range_point * sin((*trust_track)[i].elev_point/180.0*PI);
//输出平滑后的高度
Trust_Track_Output[*Trust_track_num_Output-1][0].z = static_cast<float>((*trust_track)[i].Height+Work_Parameter.Height);
Trust_Track_Output[*Trust_track_num_Output-1][0].z = static_cast<float>((*trust_track)[i].Height+rdp_parameter.radar_height);
Trust_Track_Output[*Trust_track_num_Output-1][0].Elevation = static_cast<float>((*trust_track)[i].elev_point);
@@ -158,10 +163,16 @@ int Track_Asso:: track_asso_process(std::vector<PointRecv> *point_
std::memcpy(Trust_Track_Output[*Trust_track_num_Output-1][0].speed_dim, (*trust_track)[i].speed_dim, sizeof(Trust_Track_Output[*Trust_track_num_Output-1][0].speed_dim));
std::memcpy(Trust_Track_Output[*Trust_track_num_Output-1][0].range_dim, (*trust_track)[i].range_dim, sizeof(Trust_Track_Output[*Trust_track_num_Output-1][0].range_dim));
RDP_RECORD(RdpRecTrustTrack((*trust_track)[i]));
#ifdef RDP_RECORD_ENABLE
rec_trust.push_back((*trust_track)[i]);
#endif
}
}
#ifdef RDP_RECORD_ENABLE
RDP_RECORD_TRUST_TRACKS(rec_trust);
#endif
return 0;
}
@@ -276,7 +287,7 @@ void Track_Asso:: model_interaction(std::vector <Trust_Track> *trust_track)
}
// 产生 F Q 矩阵
void Track_Asso::IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6],struct RadarPara Work_Parameter)
void Track_Asso::IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6])
{
//F
@@ -304,7 +315,7 @@ void Track_Asso::IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], doub
//模型1
//Q
double q1=Work_Parameter.Model1_Q_slow;
double q1=rdp_parameter.model_1_Q_slow;
double q111=1/(2*pow(alpha,5))*(1-exp(-2*alpha*delta_T)+2*alpha*delta_T+2*pow(alpha,3)*pow(delta_T,3)/3-2*pow(alpha,2)*pow(delta_T,2)-4*alpha*delta_T*exp(-alpha*delta_T));
double q121=1/(2*pow(alpha,4))*(exp(-2*alpha*delta_T)+1-2*exp(-alpha*delta_T)+2*alpha*delta_T*exp(-alpha*delta_T)-2*alpha*delta_T+pow(alpha,2)*pow(delta_T,2));
double q131=1/(2*pow(alpha,3))*(1-exp(-2*alpha*delta_T)-2*alpha*delta_T*exp(-alpha*delta_T));
@@ -325,24 +336,24 @@ void Track_Asso::IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], doub
double q2;
if(v_track<=5)
{
q2=Work_Parameter.Model2_Q_slow/20;
q2=rdp_parameter.model_2_Q_slow/20;
}
else if(v_track<=15&&v_track>5)
{
q2=Work_Parameter.Model2_Q_slow;
q2=rdp_parameter.model_2_Q_slow;
}
else if(v_track<=30&&v_track>15)
{
q2=Work_Parameter.Model2_Q_slow;
q2=rdp_parameter.model_2_Q_slow;
}
else if(v_track<=100&&v_track>30)
{
q2=Work_Parameter.Model2_Q_fast;
q2=rdp_parameter.model_2_Q_fast;
}
else
{
q2=Work_Parameter.Model2_Q_fast;
q2=rdp_parameter.model_2_Q_fast;
}
double q112=1/(2*pow(alpha,5))*(1-exp(-2*alpha*delta_T)+2*alpha*delta_T+2*pow(alpha,3)*pow(delta_T,3)/3-2*pow(alpha,2)*pow(delta_T,2)-4*alpha*delta_T*exp(-alpha*delta_T));
@@ -366,29 +377,29 @@ void Track_Asso::IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], doub
double q3;
if(v_track<=5)
{
q3=Work_Parameter.Model3_Q_slow/20;
q3=rdp_parameter.model_3_Q_slow/20;
}
else if(v_track<=15&&v_track>5)
{
q3=Work_Parameter.Model3_Q_slow;
q3=rdp_parameter.model_3_Q_slow;
}
else if(v_track<=20&&v_track>15)
{
q3=Work_Parameter.Model3_Q_slow;
q3=rdp_parameter.model_3_Q_slow;
}
else if(v_track<=50&&v_track>20)
{
q3=Work_Parameter.Model3_Q_fast;
q3=rdp_parameter.model_3_Q_fast;
}
else if(v_track<=100&&v_track>50)
{
q3=2*Work_Parameter.Model3_Q_fast;
q3=2*rdp_parameter.model_3_Q_fast;
}
else
{
q3=10*Work_Parameter.Model3_Q_fast;
q3=10*rdp_parameter.model_3_Q_fast;
}
double q113=1/(2*pow(alpha,5))*(1-exp(-2*alpha*delta_T)+2*alpha*delta_T+2*pow(alpha,3)*pow(delta_T,3)/3-2*pow(alpha,2)*pow(delta_T,2)-4*alpha*delta_T*exp(-alpha*delta_T));
@@ -416,11 +427,10 @@ void Track_Asso::IMM_d_cal(double v_track,
double X3[6], double P3[6][6],
double Z[3], double prt,double freq_ind,
double delta_T,
double *d1,double *d2,double *d3,
struct RadarPara Work_Parameter)
double *d1,double *d2,double *d3)
{
double F[6][6], Q1[6][6], Q2[6][6], Q3[6][6];
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3,Work_Parameter);
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3);
kalman Kalman;
*d1=Kalman.d_cal_EKF(F,Q1,Z,X1,P1,prt,freq_ind);
@@ -429,11 +439,24 @@ void Track_Asso::IMM_d_cal(double v_track,
}
void Track_Asso:: model_filter(std::vector <Trust_Track> *trust_track,struct RadarPara Work_Parameter)
void Track_Asso:: model_filter(std::vector <Trust_Track> *trust_track, long long latest_timestamp)
{
std::vector <Asso_Info> associated_info;
#ifdef RDP_RECORD_ENABLE
//记录未关联上的点航信息:收集到分块上限即入队,函数末尾再 flush 剩余
//按本圈候选数(点迹数×航迹数,封顶到分块上限)预留,避免收集期间多次扩容
std::vector<Asso_Info> rec_asso;
{
std::size_t est = point_process.size() * trust_track->size();
if (est > static_cast<std::size_t>(RDP_RECORD_ASSO_CHUNK_ROWS))
{
est = static_cast<std::size_t>(RDP_RECORD_ASSO_CHUNK_ROWS);
}
rec_asso.reserve(est);
}
#endif
//遍历所有点迹航迹 计算点迹和航迹的统计距离
for (size_t loop_of_track = 0; loop_of_track<trust_track->size();loop_of_track++)
@@ -467,17 +490,17 @@ void Track_Asso:: model_filter(std::vector <Trust_Track> *trust_track,struct Ra
//计算统计距离d
double d1,d2,d3;
IMM_d_cal(v_track,X1,P1,X2,P2, X3,P3,Z,prt,freq_ind,delta_T,&d1,&d2,&d3,Work_Parameter);
IMM_d_cal(v_track,X1,P1,X2,P2, X3,P3,Z,prt,freq_ind,delta_T,&d1,&d2,&d3);
//判定统计距离门限,thd_d
bool thd_d = false;
if( (r_track <= 500 && \
(d1*d1 < ASSO_THORD*ASSO_THORD || d2*d2 < ASSO_THORD*ASSO_THORD || d3*d3 < ASSO_THORD*ASSO_THORD / 1000.0)) || \
(d1*d1 < rdp_parameter.associate_threshold*rdp_parameter.associate_threshold || d2*d2 < rdp_parameter.associate_threshold*rdp_parameter.associate_threshold || d3*d3 < rdp_parameter.associate_threshold*rdp_parameter.associate_threshold / 1000.0)) || \
((r_track > 500 && r_track < 1000) && \
(d1*d1 < ASSO_THORD*ASSO_THORD || d2*d2 < ASSO_THORD*ASSO_THORD || d3*d3 < ASSO_THORD*ASSO_THORD / 100.0)) || \
(d1*d1 < rdp_parameter.associate_threshold*rdp_parameter.associate_threshold || d2*d2 < rdp_parameter.associate_threshold*rdp_parameter.associate_threshold || d3*d3 < rdp_parameter.associate_threshold*rdp_parameter.associate_threshold / 100.0)) || \
(r_track >= 1000 && \
(d1*d1 < ASSO_THORD*ASSO_THORD || d2*d2 < ASSO_THORD*ASSO_THORD || d3*d3 < ASSO_THORD*ASSO_THORD))\
(d1*d1 < rdp_parameter.associate_threshold*rdp_parameter.associate_threshold || d2*d2 < rdp_parameter.associate_threshold*rdp_parameter.associate_threshold || d3*d3 < rdp_parameter.associate_threshold*rdp_parameter.associate_threshold))\
){
thd_d = true;
}
@@ -538,6 +561,7 @@ void Track_Asso:: model_filter(std::vector <Trust_Track> *trust_track,struct Ra
associated_info_tmp.delta_h=delta_h;
associated_info_tmp.R=R;
associated_info_tmp.VT=VT;
associated_info_tmp.tas_flag=point_process[loop_of_point].point_type;
associated_info_tmp.thd_d=thd_d;
associated_info_tmp.thd_h=thd_h;
associated_info_tmp.thd_r=thd_r;
@@ -548,7 +572,13 @@ void Track_Asso:: model_filter(std::vector <Trust_Track> *trust_track,struct Ra
{
associated_info.push_back(associated_info_tmp);
}else{
RDP_RECORD(RdpRecAssoInfo(associated_info_tmp));
#ifdef RDP_RECORD_ENABLE
rec_asso.push_back(associated_info_tmp);
if (rec_asso.size() >= RDP_RECORD_ASSO_CHUNK_ROWS)
{
RDP_RECORD_ASSO_INFOS(rec_asso);
}
#endif
}
}
}
@@ -591,7 +621,7 @@ void Track_Asso:: model_filter(std::vector <Trust_Track> *trust_track,struct Ra
double v_track = sqrt(pow((*trust_track)[track_index-1].X[1],2)+pow((*trust_track)[track_index-1].X[4],2));
double delta_T = (point_process[point_index-1].CPI_time - (*trust_track)[track_index-1].T_track)/1000.0;
double F[6][6], Q1[6][6], Q2[6][6], Q3[6][6];
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3,Work_Parameter);
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3);
if (delta_T > 0)
{
@@ -654,9 +684,18 @@ void Track_Asso:: model_filter(std::vector <Trust_Track> *trust_track,struct Ra
(*trust_track)[track_index-1].P3[ii][jj]=P3_filter[ii][jj];
}
//计算航迹质量
double d_tmp = exp(-associated_info[min_index-1].d_min);
double snr_tmp = 1/(1+exp(-(point_process[point_index-1].SNR - 16) / 1.2));
double len_tmp = 1/(1+exp(-((*trust_track)[track_index-1].Valid_point_sum - 4) / 1.2)) * 0.5 + (1 - (*trust_track)[track_index-1].Valid_point_sum / (*trust_track)[track_index-1].Point_sum) * 0.5;
double tq_tmp = rdp_parameter.track_quality_d_weight * d_tmp + rdp_parameter.track_quality_snr_weight * snr_tmp + rdp_parameter.track_quality_len_weight * len_tmp;
(*trust_track)[track_index-1].Quality = (*trust_track)[track_index-1].Quality * rdp_parameter.track_quality_iter_coeff + tq_tmp * (1 - rdp_parameter.track_quality_iter_coeff);
//更新航迹时间
(*trust_track)[track_index-1].T_track = point_process[point_index-1].CPI_time;
(*trust_track)[track_index-1].GNSS_time = point_process[point_index-1].GNSS_time;
(*trust_track)[track_index-1].Unupdated_time = 0;
//更新关联上的点迹信息
(*trust_track)[track_index-1].point_id = point_process[point_index-1].point_id;
@@ -686,6 +725,8 @@ void Track_Asso:: model_filter(std::vector <Trust_Track> *trust_track,struct Ra
(*trust_track)[track_index-1].point_flag=1; //实点
(*trust_track)[track_index-1].associate_point_number = (*trust_track)[track_index-1].associate_point_number+1; //关联点数+1
(*trust_track)[track_index-1].Track_Update_Flag = 1; //航迹更新标志
(*trust_track)[track_index-1].Point_sum = (*trust_track)[track_index-1].Point_sum + 1;
(*trust_track)[track_index-1].Valid_point_sum = (*trust_track)[track_index-1].Valid_point_sum + 1;
}
//删除associated_info中关联上的航迹 点迹信息
@@ -693,7 +734,13 @@ void Track_Asso:: model_filter(std::vector <Trust_Track> *trust_track,struct Ra
{
if(associated_info[ii].track_index==track_index)
{
RDP_RECORD(RdpRecAssoInfo(associated_info[ii]));
#ifdef RDP_RECORD_ENABLE
rec_asso.push_back(associated_info[ii]);
if (rec_asso.size() >= RDP_RECORD_ASSO_CHUNK_ROWS)
{
RDP_RECORD_ASSO_INFOS(rec_asso);
}
#endif
associated_info.erase(associated_info.begin()+ii);
associated_num=static_cast<int>(associated_info.size());
}
@@ -705,7 +752,13 @@ void Track_Asso:: model_filter(std::vector <Trust_Track> *trust_track,struct Ra
{
if(associated_info[ii].point_index==point_index)
{
RDP_RECORD(RdpRecAssoInfo(associated_info[ii]));
#ifdef RDP_RECORD_ENABLE
rec_asso.push_back(associated_info[ii]);
if (rec_asso.size() >= RDP_RECORD_ASSO_CHUNK_ROWS)
{
RDP_RECORD_ASSO_INFOS(rec_asso);
}
#endif
associated_info.erase(associated_info.begin()+ii);
associated_num=static_cast<int>(associated_info.size());
}
@@ -724,30 +777,17 @@ void Track_Asso:: model_filter(std::vector <Trust_Track> *trust_track,struct Ra
{
if((*trust_track)[i].point_flag == 0 && (*trust_track)[i].manual_tracking_flag == 0) //未关联上点迹的航迹进行外推
{
double delta_T = 0;
delta_T = rdp_parameter.data_rate_tws;
//外推点数未到上限,则继续外推,否则不外推
if((*trust_track)[i].Extrapolate_round <= MAX_EXTRAPOLATE_NUM)
if((*trust_track)[i].Extrapolate_round <= rdp_parameter.extrapolate_max)
{
double v_track = sqrt(pow((*trust_track)[i].X[1],2)+pow((*trust_track)[i].X[4],2));
double delta_T;
if(Work_Parameter.work_mode == 0)
{
delta_T = Work_Parameter.DATA_RATE_SHORT;
}
else if(Work_Parameter.work_mode == 1)
{
delta_T = Work_Parameter.DATA_RATE_MIDDLE;
}
else if(Work_Parameter.work_mode == 2)
{
delta_T = Work_Parameter.DATA_RATE_FAR;
}
else
{
delta_T = Work_Parameter.DATA_RATE_SHORT;
}
double F[6][6], Q1[6][6], Q2[6][6], Q3[6][6];
IMM_F_Q_gen( v_track, delta_T, F, Q1, Q2, Q3,Work_Parameter);
IMM_F_Q_gen( v_track, delta_T, F, Q1, Q2, Q3);
//航迹X P
double X1[6],X2[6],X3[6],P1[6][6],P2[6][6],P3[6][6];
@@ -759,7 +799,6 @@ void Track_Asso:: model_filter(std::vector <Trust_Track> *trust_track,struct Ra
memcpy(P3,(*trust_track)[i].P3,6*6*sizeof(double));
//外推
double X1_pred[6],X2_pred[6],X3_pred[6],P1_pred[6][6],P2_pred[6][6],P3_pred[6][6];
kalman Kalman;
Kalman.kalman_pred(F, Q1, X1, P1, X1_pred, P1_pred);
@@ -772,6 +811,7 @@ void Track_Asso:: model_filter(std::vector <Trust_Track> *trust_track,struct Ra
memcpy((*trust_track)[i].P2,P2_pred,6*6*sizeof(double));
memcpy((*trust_track)[i].P3,P3_pred,6*6*sizeof(double));
(*trust_track)[i].circle_id++;
(*trust_track)[i].T_track = static_cast<long long>((*trust_track)[i].T_track+delta_T*1000.0); //更新航迹时间
(*trust_track)[i].GNSS_time = static_cast<long long>((*trust_track)[i].GNSS_time+delta_T*1000.0); //更新航迹时间
(*trust_track)[i].Extrapolate_round =(*trust_track)[i].Extrapolate_round+1; //连续未用实点更新时间
@@ -782,9 +822,25 @@ void Track_Asso:: model_filter(std::vector <Trust_Track> *trust_track,struct Ra
(*trust_track)[i].point_flag=0; //虚点
(*trust_track)[i].Track_Update_Flag = 0; //航迹更新标志
}
}
}
//航迹质量递减
double t = (*trust_track)[i].Unupdated_time / 1000.0;
(*trust_track)[i].Quality -= t * t * rdp_parameter.track_quality_decrease_coeff_o2 + t * rdp_parameter.track_quality_decrease_coeff_o1;
(*trust_track)[i].Unupdated_time += static_cast<long long>(delta_T*1000.0);
t = (*trust_track)[i].Unupdated_time / 1000.0;
(*trust_track)[i].Quality += t * t * rdp_parameter.track_quality_decrease_coeff_o2 + t * rdp_parameter.track_quality_decrease_coeff_o1;
(*trust_track)[i].Quality = (*trust_track)[i].Quality < 0.0 ? 0.0 : (*trust_track)[i].Quality;
(*trust_track)[i].Point_sum = (*trust_track)[i].Point_sum + 1;
}
}
#ifdef RDP_RECORD_ENABLE
//函数返回前把剩余未入队的关联信息 flush 掉(宏内部 std::move,调用后 rec_asso 为空)
if (!rec_asso.empty())
{
RDP_RECORD_ASSO_INFOS(rec_asso);
}
#endif
}
void Track_Asso:: model_output(std::vector <Trust_Track> *trust_track)
@@ -902,19 +958,19 @@ void Track_Asso:: track_hight_update(int updata_
if((*trust_track)[updata_track_index-1].range_point <= 1000)
{
height_win_length = H_F_WIN_LEN+2;
height_win_length = rdp_parameter.height_smooth_win+2;
}
else if((*trust_track)[updata_track_index-1].range_point <= 2000 && (*trust_track)[updata_track_index-1].range_point > 1000)
{
height_win_length = H_F_WIN_LEN+3;
height_win_length = rdp_parameter.height_smooth_win+3;
}
else if((*trust_track)[updata_track_index-1].range_point <= 4000 && (*trust_track)[updata_track_index-1].range_point > 2000)
{
height_win_length = H_F_WIN_LEN+4;
height_win_length = rdp_parameter.height_smooth_win+4;
}
else
{
height_win_length = H_F_WIN_LEN+6;
height_win_length = rdp_parameter.height_smooth_win+6;
}
double sum=0;
+4 -5
View File
@@ -16,7 +16,7 @@ public:
std::vector <Trust_Track> *trust_track, //航迹文件
struct Track Trust_Track_Output[MAX_TRACK_NUM][10], //更新航迹信息
int *Trust_track_num_Output, //更新航迹数
struct RadarPara Work_Parameter //工作参数
long long latest_timestamp //最新时间戳
);
Track_Asso();
@@ -30,7 +30,7 @@ private:
void model_interaction(std::vector <Trust_Track> *trust_track); //模型交互
void model_filter(std::vector <Trust_Track> *trust_track, //滤波
struct RadarPara Work_Parameter
long long latest_timestamp
);
void model_output(std::vector <Trust_Track> *trust_track ); //模型输出
@@ -41,10 +41,9 @@ private:
double X3[6], double P3[6][6],
double Z[3], double prt,double freq_ind,
double delta_T,
double *d1,double *d2,double *d3,
struct RadarPara Work_Parameter);
double *d1,double *d2,double *d3);
void IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6],struct RadarPara Work_Parameter);
void IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6]);
double Pt[3][3]; //模型转移概率
@@ -11,8 +11,7 @@ using namespace std;
int Track_Asso_Direct_Tracking::track_asso_process_direct_tracking(std::vector<PointRecv> *point_recv, //点迹
Trust_Track *trust_track, //航迹
struct Track Trust_Track_Output[MAX_TRACK_NUM][10], //更新航迹信息
int *Trust_track_num_Output, //更新航迹数
struct RadarPara Work_Parameter //工作参数
int *Trust_track_num_Output //更新航迹数
)
{
@@ -25,7 +24,7 @@ int Track_Asso_Direct_Tracking::track_asso_process_direct_tracking(std::vector<P
//IMM算法
model_interaction(trust_track);
model_filter(trust_track, Work_Parameter);
model_filter(trust_track);
model_output(trust_track);
@@ -38,8 +37,8 @@ int Track_Asso_Direct_Tracking::track_asso_process_direct_tracking(std::vector<P
Trust_Track_Output[*Trust_track_num_Output-1][0].Point_Sum=1;
double x,y;
x=(*trust_track).X[0]+(*trust_track).X[1]*Work_Parameter.Sys_delay;
y=(*trust_track).X[3]+(*trust_track).X[4]*Work_Parameter.Sys_delay;
x=(*trust_track).X[0]+(*trust_track).X[1]*rdp_parameter.radar_system_delay;
y=(*trust_track).X[3]+(*trust_track).X[4]*rdp_parameter.radar_system_delay;
coor_trans Coor_trans;
double r, azi;
Coor_trans.cart2polar(x, y, &r, &azi);
@@ -58,7 +57,7 @@ int Track_Asso_Direct_Tracking::track_asso_process_direct_tracking(std::vector<P
Trust_Track_Output[*Trust_track_num_Output-1][0].Track_Mode=(*trust_track).Track_Mode;
Trust_Track_Output[*Trust_track_num_Output-1][0].track_time=(*trust_track).T_track/1000.0;
Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Point = (*trust_track).point_flag;
Trust_Track_Output[*Trust_track_num_Output-1][0].z=(*trust_track).Height+Work_Parameter.Height;
Trust_Track_Output[*Trust_track_num_Output-1][0].z=(*trust_track).Height+rdp_parameter.radar_height;
double Direction_Angle;
Direction_Angle=atan2((*trust_track).X[4], (*trust_track).X[1]);
@@ -185,8 +184,7 @@ void Track_Asso_Direct_Tracking::model_interaction(Trust_Track *trust_track)
}
void Track_Asso_Direct_Tracking::model_filter(Trust_Track *trust_track, //滤波
struct RadarPara Work_Parameter)
void Track_Asso_Direct_Tracking::model_filter(Trust_Track *trust_track) //滤波
{
//存储关联信息
struct asso_info
@@ -234,7 +232,7 @@ void Track_Asso_Direct_Tracking::model_filter(Trust_Track *trust_track,
//计算统计距离d
double d1,d2,d3;
IMM_d_cal(v_track,X1,P1,X2,P2, X3,P3,Z,prt,freq_ind,delta_T,&d1,&d2,&d3,Work_Parameter);
IMM_d_cal(v_track,X1,P1,X2,P2, X3,P3,Z,prt,freq_ind,delta_T,&d1,&d2,&d3);
//计算高度门限
double d_h;
@@ -290,7 +288,7 @@ void Track_Asso_Direct_Tracking::model_filter(Trust_Track *trust_track,
}
//小于关联门限 保存关联信息
if((d1*d1<ASSO_THORD*ASSO_THORD || d2*d2<ASSO_THORD*ASSO_THORD || d3*d3<ASSO_THORD*ASSO_THORD)
if((d1*d1<rdp_parameter.associate_threshold*rdp_parameter.associate_threshold || d2*d2<rdp_parameter.associate_threshold*rdp_parameter.associate_threshold || d3*d3<rdp_parameter.associate_threshold*rdp_parameter.associate_threshold)
&& d_h == 1 )
{
struct asso_info associated_info_tmp;
@@ -337,7 +335,7 @@ void Track_Asso_Direct_Tracking::model_filter(Trust_Track *trust_track,
// Z[1]=point_process[point_index-1].Range*sin(point_process[point_index-1].Azimuth);
double delta_T = (point_process[point_index-1].CPI_time - T_track)/1000.0;
double F[6][6], Q1[6][6], Q2[6][6], Q3[6][6];
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3,Work_Parameter);
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3);
double X1_filter[6],X2_filter[6],X3_filter[6],P1_filter[6][6],P2_filter[6][6],P3_filter[6][6];
double S1[3][3] = {{0}},S2[3][3] = {{0}},S3[3][3] = {{0}};
@@ -424,9 +422,9 @@ void Track_Asso_Direct_Tracking::model_filter(Trust_Track *trust_track,
//未关联上,航迹外推
else
{
double delta_T = Work_Parameter.DATA_RATE_TAS/1000.0;
double delta_T = rdp_parameter.data_rate_tas/1000.0;
double F[6][6], Q1[6][6], Q2[6][6], Q3[6][6];
IMM_F_Q_gen( v_track, delta_T, F, Q1, Q2, Q3,Work_Parameter);
IMM_F_Q_gen( v_track, delta_T, F, Q1, Q2, Q3);
double X1_pred[6],X2_pred[6],X3_pred[6],P1_pred[6][6],P2_pred[6][6],P3_pred[6][6];
kalman Kalman;
@@ -510,7 +508,7 @@ void Track_Asso_Direct_Tracking::model_output(Trust_Track *trust_track)
}
// 产生 F Q 矩阵
void Track_Asso_Direct_Tracking::IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6],struct RadarPara Work_Parameter)
void Track_Asso_Direct_Tracking::IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6])
{
//F
@@ -538,7 +536,7 @@ void Track_Asso_Direct_Tracking::IMM_F_Q_gen(double v_track, double delta_T,doub
//模型1
//Q
double q1=Work_Parameter.Model1_Q_slow;
double q1=rdp_parameter.model_1_Q_slow;
double q111=1/(2*pow(alpha,5))*(1-exp(-2*alpha*delta_T)+2*alpha*delta_T+2*pow(alpha,3)*pow(delta_T,3)/3-2*pow(alpha,2)*pow(delta_T,2)-4*alpha*delta_T*exp(-alpha*delta_T));
double q121=1/(2*pow(alpha,4))*(exp(-2*alpha*delta_T)+1-2*exp(-alpha*delta_T)+2*alpha*delta_T*exp(-alpha*delta_T)-2*alpha*delta_T+pow(alpha,2)*pow(delta_T,2));
double q131=1/(2*pow(alpha,3))*(1-exp(-2*alpha*delta_T)-2*alpha*delta_T*exp(-alpha*delta_T));
@@ -559,15 +557,15 @@ void Track_Asso_Direct_Tracking::IMM_F_Q_gen(double v_track, double delta_T,doub
double q2;
if(v_track<=5)
{
q2=Work_Parameter.Model2_Q_slow/20;
q2=rdp_parameter.model_2_Q_slow/20;
}
else if(v_track<=100&&v_track>50)
{
q2=10.0*Work_Parameter.Model2_Q_fast;
q2=10.0*rdp_parameter.model_2_Q_fast;
}
else
{
q2=Work_Parameter.Model2_Q_fast;
q2=rdp_parameter.model_2_Q_fast;
}
double q112=1/(2*pow(alpha,5))*(1-exp(-2*alpha*delta_T)+2*alpha*delta_T+2*pow(alpha,3)*pow(delta_T,3)/3-2*pow(alpha,2)*pow(delta_T,2)-4*alpha*delta_T*exp(-alpha*delta_T));
@@ -591,15 +589,15 @@ void Track_Asso_Direct_Tracking::IMM_F_Q_gen(double v_track, double delta_T,doub
double q3;
if(v_track<=5)
{
q3=Work_Parameter.Model3_Q_slow/20;
q3=rdp_parameter.model_3_Q_slow/20;
}
else if(v_track<=100&&v_track>50)
{
q3=10.0*Work_Parameter.Model3_Q_fast;
q3=10.0*rdp_parameter.model_3_Q_fast;
}
else
{
q3=Work_Parameter.Model3_Q_fast;
q3=rdp_parameter.model_3_Q_fast;
}
double q113=1/(2*pow(alpha,5))*(1-exp(-2*alpha*delta_T)+2*alpha*delta_T+2*pow(alpha,3)*pow(delta_T,3)/3-2*pow(alpha,2)*pow(delta_T,2)-4*alpha*delta_T*exp(-alpha*delta_T));
double q123=1/(2*pow(alpha,4))*(exp(-2*alpha*delta_T)+1-2*exp(-alpha*delta_T)+2*alpha*delta_T*exp(-alpha*delta_T)-2*alpha*delta_T+pow(alpha,2)*pow(delta_T,2));
@@ -626,11 +624,10 @@ void Track_Asso_Direct_Tracking::IMM_d_cal(double v_track,
double X3[6], double P3[6][6],
double Z[3], double prt,double freq_ind,
double delta_T,
double *d1,double *d2,double *d3,
struct RadarPara Work_Parameter)
double *d1,double *d2,double *d3)
{
double F[6][6], Q1[6][6], Q2[6][6], Q3[6][6];
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3,Work_Parameter);
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3);
kalman Kalman;
// *d1=Kalman.d_cal_with_doppler(F,Q1,Z,X1,P1,v_point,prt,freq_ind);
@@ -653,19 +650,19 @@ void Track_Asso_Direct_Tracking::track_hight_update(int asso_point
int height_win_length;
if((*trust_track).range_point <= 1000)
{
height_win_length = H_F_WIN_LEN+2;
height_win_length = rdp_parameter.height_smooth_win+2;
}
else if((*trust_track).range_point <= 2000 && (*trust_track).range_point > 1000)
{
height_win_length = H_F_WIN_LEN+3;
height_win_length = rdp_parameter.height_smooth_win+3;
}
else if((*trust_track).range_point <= 4000 && (*trust_track).range_point > 2000)
{
height_win_length = H_F_WIN_LEN+4;
height_win_length = rdp_parameter.height_smooth_win+4;
}
else
{
height_win_length = H_F_WIN_LEN+6;
height_win_length = rdp_parameter.height_smooth_win+6;
}
double sum=0;
@@ -12,8 +12,7 @@ class Track_Asso_Direct_Tracking
int track_asso_process_direct_tracking(std::vector<PointRecv> *point_recv, //点迹
Trust_Track *trust_track, //航迹
struct Track Trust_Track_Output[MAX_TRACK_NUM][10], //更新航迹信息
int *Trust_track_num_Output, //更新航迹数
struct RadarPara Work_Parameter //工作参数
int *Trust_track_num_Output //更新航迹数
);
Track_Asso_Direct_Tracking();
@@ -27,8 +26,7 @@ class Track_Asso_Direct_Tracking
//IMM
void model_interaction(Trust_Track *trust_track); //模型交互
void model_filter(Trust_Track *trust_track, //滤波
struct RadarPara Work_Parameter
void model_filter(Trust_Track *trust_track //滤波
);
void model_output(Trust_Track *trust_track); //模型输出
@@ -39,10 +37,9 @@ class Track_Asso_Direct_Tracking
double X3[6], double P3[6][6],
double Z[3], double prt,double freq_ind,
double delta_T,
double *d1,double *d2,double *d3,
struct RadarPara Work_Parameter);
double *d1,double *d2,double *d3);
void IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6],struct RadarPara Work_Parameter);
void IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6]);
double Pt[3][3]; //模型转移概率
+102 -46
View File
@@ -6,6 +6,9 @@
#include <cstring>
#include <vector>
#include <Eigen/Dense>
#include "rdp_record.h"
using namespace Eigen;
using namespace std;
@@ -13,7 +16,6 @@ int Track_Asso_Tas::track_asso_process_tas(std::vector<PointRecv>
std::vector<Trust_Track> *trust_track, //航迹文件
struct Track Trust_Track_Output[MAX_TRACK_NUM][10], //更新航迹信息
int *Trust_track_num_Output, //更新航迹数
struct RadarPara Work_Parameter, //工作参数
int tas_track_idx,
long long latest_timestamp //最新时间戳
)
@@ -29,7 +31,7 @@ long long latest_timestamp //最新时间戳
//IMM算法
model_interaction(trust_track,tas_track_idx);
model_filter(trust_track, Work_Parameter,tas_track_idx, latest_timestamp);
model_filter(trust_track,tas_track_idx, latest_timestamp);
model_output(trust_track,tas_track_idx);
@@ -37,6 +39,11 @@ long long latest_timestamp //最新时间戳
std::vector<PointRecv>().swap(point_process);
//输出航迹
#ifdef RDP_RECORD_ENABLE
//记录可靠航迹:先收集,输出循环结束后一次入队
std::vector<Trust_Track> rec_trust;
rec_trust.reserve(trust_track->size());
#endif
for (size_t i=0;i<trust_track->size();i++)
{ // 输出更新航迹条件:
if((*trust_track)[i].Track_Index == tas_track_idx && (*trust_track)[i].Track_Update_Flag == 1)
@@ -46,8 +53,8 @@ long long latest_timestamp //最新时间戳
Trust_Track_Output[*Trust_track_num_Output-1][0].Point_Sum=1;
double x,y;
x=(*trust_track)[i].X[0]+(*trust_track)[i].X[1]*Work_Parameter.Sys_delay;
y=(*trust_track)[i].X[3]+(*trust_track)[i].X[4]*Work_Parameter.Sys_delay;
x=(*trust_track)[i].X[0]+(*trust_track)[i].X[1]*rdp_parameter.radar_system_delay;
y=(*trust_track)[i].X[3]+(*trust_track)[i].X[4]*rdp_parameter.radar_system_delay;
coor_trans Coor_trans;
double r, azi;
Coor_trans.cart2polar(x, y, &r, &azi);
@@ -67,7 +74,7 @@ long long latest_timestamp //最新时间戳
Trust_Track_Output[*Trust_track_num_Output-1][0].track_time=static_cast<float>((*trust_track)[i].T_track/1000.0);
Trust_Track_Output[*Trust_track_num_Output-1][0].GNSS_time=(*trust_track)[i].GNSS_time;
Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Point = (*trust_track)[i].point_flag;
Trust_Track_Output[*Trust_track_num_Output-1][0].z=static_cast<float>((*trust_track)[i].Height+Work_Parameter.Height);
Trust_Track_Output[*Trust_track_num_Output-1][0].z=static_cast<float>((*trust_track)[i].Height+rdp_parameter.radar_height);
double Direction_Angle;
Direction_Angle=atan2((*trust_track)[i].X[4], (*trust_track)[i].X[1]);
@@ -109,10 +116,23 @@ long long latest_timestamp //最新时间戳
std::memcpy(Trust_Track_Output[*Trust_track_num_Output-1][0].speed_dim, (*trust_track)[i].speed_dim, sizeof(Trust_Track_Output[*Trust_track_num_Output-1][0].speed_dim));
std::memcpy(Trust_Track_Output[*Trust_track_num_Output-1][0].range_dim, (*trust_track)[i].range_dim, sizeof(Trust_Track_Output[*Trust_track_num_Output-1][0].range_dim));
(*trust_track)[i].Range = Trust_Track_Output[*Trust_track_num_Output-1][0].Range;
(*trust_track)[i].Azimuth = Trust_Track_Output[*Trust_track_num_Output-1][0].Azimuth;
(*trust_track)[i].Elevation = Trust_Track_Output[*Trust_track_num_Output-1][0].Elevation;
(*trust_track)[i].Velocity = Trust_Track_Output[*Trust_track_num_Output-1][0].Range_V;
#ifdef RDP_RECORD_ENABLE
rec_trust.push_back((*trust_track)[i]);
#endif
}
}
#ifdef RDP_RECORD_ENABLE
RDP_RECORD_TRUST_TRACKS(rec_trust);
#endif
return 0;
}
@@ -235,21 +255,16 @@ void Track_Asso_Tas:: model_interaction( std::vector<Trust_Track> *trust_track
}
void Track_Asso_Tas::model_filter( std::vector<Trust_Track> *trust_track, //滤波
struct RadarPara Work_Parameter,
int tas_track_idx,
long long latest_timestamp //最新时间戳
)
{
//存储关联信息
struct asso_info
{
double d1;
double d2;
double d3;
double d_min;
int point_index;
};
std::vector <asso_info> associated_info;
std::vector <Asso_Info> associated_info;
#ifdef RDP_RECORD_ENABLE
//记录未关联上的点航信息:收集到分块上限即入队,函数末尾再 flush 剩余
std::vector<Asso_Info> rec_asso;
rec_asso.reserve(point_process.size());
#endif
//航迹信息
double X1[6] = {0},X2[6] = {0},X3[6] = {0},P1[6][6] = {{0}},P2[6][6] = {{0}},P3[6][6] = {{0}};
@@ -297,7 +312,7 @@ void Track_Asso_Tas::model_filter( std::vector<Trust_Track> *t
double freq_ind = point_process[loop_of_point].freq_index;
double delta_T = (point_process[loop_of_point].CPI_time - T_track)/1000.0;
if (delta_T <= 0){
RDP_LOG << "Warning: delta_T <= 0, skipping point association for point index " << point_process[loop_of_point].CPI_time << " " << T_track << std::endl;
RDP_LOG_WARN("Warning: delta_T <= 0, skipping point association for point index {} {}", point_process[loop_of_point].CPI_time, T_track);
continue;
}
@@ -305,12 +320,12 @@ void Track_Asso_Tas::model_filter( std::vector<Trust_Track> *t
//计算统计距离d
double d1,d2,d3;
IMM_d_cal(v_track,X1,P1,X2,P2, X3,P3,Z,prt,freq_ind,delta_T,&d1,&d2,&d3,Work_Parameter);
IMM_d_cal(v_track,X1,P1,X2,P2, X3,P3,Z,prt,freq_ind,delta_T,&d1,&d2,&d3);
//判定统计距离门限,thd_d
bool thd_d = false;
if(d1*d1<ASSO_THORD*ASSO_THORD || d2*d2<ASSO_THORD*ASSO_THORD || d3*d3<ASSO_THORD*ASSO_THORD){
if(d1*d1<rdp_parameter.associate_threshold*rdp_parameter.associate_threshold || d2*d2<rdp_parameter.associate_threshold*rdp_parameter.associate_threshold || d3*d3<rdp_parameter.associate_threshold*rdp_parameter.associate_threshold){
thd_d = true;
}
@@ -327,31 +342,51 @@ void Track_Asso_Tas::model_filter( std::vector<Trust_Track> *t
thd_h = true;
}
//小于关联门限 保存关联信息
if(thd_d && thd_h)
{
// std::cout << "TAS asoooooo111" ;
struct asso_info associated_info_tmp;
struct Asso_Info associated_info_tmp;
associated_info_tmp.d1=d1;
associated_info_tmp.d2=d2;
associated_info_tmp.d3=d3;
associated_info_tmp.point_index=static_cast<int>(loop_of_point)+1;
associated_info_tmp.track_index=tas_track_idx;
if(d1<=d2&&d1<=d3)
associated_info_tmp.d_min=d1;
else if(d2<=d1&& d2<=d3)
associated_info_tmp.d_min=d2;
else
associated_info_tmp.d_min=d3;
associated_info_tmp.point_id=point_process[loop_of_point].point_id;
associated_info_tmp.track_id=(*trust_track)[tas_track_idx].track_id;
associated_info_tmp.circle_id=point_process[loop_of_point].circle_id;
associated_info_tmp.delta_T=delta_T;
associated_info_tmp.delta_h=delta_h;
associated_info_tmp.R=0;
associated_info_tmp.VT=0;
associated_info_tmp.tas_flag=point_process[loop_of_point].point_type;
associated_info_tmp.thd_d=thd_d;
associated_info_tmp.thd_h=thd_h;
associated_info_tmp.thd_r=true;
associated_info_tmp.min_d_flag=false;
//小于关联门限 保存关联信息
if(thd_d && thd_h)
{
associated_info.push_back(associated_info_tmp);
}else{
#ifdef RDP_RECORD_ENABLE
rec_asso.push_back(associated_info_tmp);
if (rec_asso.size() >= RDP_RECORD_ASSO_CHUNK_ROWS)
{
RDP_RECORD_ASSO_INFOS(rec_asso);
}
#endif
}
}
//有关联 滤波
if (associated_info.size()>0)
{
//RDP_LOG << "associated point for track index " << tas_track_idx << std::endl;
//RDP_LOG_DEBUG("associated point for track index {}", tas_track_idx);
// std::cout << "TAS asoooooo222" ;
//找最近点
@@ -370,6 +405,8 @@ void Track_Asso_Tas::model_filter( std::vector<Trust_Track> *t
double d2=associated_info[min_index-1].d2;
double d3=associated_info[min_index-1].d3;
associated_info[min_index-1].min_d_flag=true;
//卡尔曼滤波
double Z[3]={point_process[point_index-1].range,point_process[point_index-1].azimuth,point_process[point_index-1].velocity};
double prt = point_process[point_index-1].PRF_index;
@@ -379,7 +416,7 @@ void Track_Asso_Tas::model_filter( std::vector<Trust_Track> *t
// Z[1]=point_process[point_index-1].Range*sin(point_process[point_index-1].Azimuth);
double delta_T = (point_process[point_index-1].CPI_time - T_track)/1000.0;
double F[6][6], Q1[6][6], Q2[6][6], Q3[6][6];
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3,Work_Parameter);
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3);
double X1_filter[6],X2_filter[6],X3_filter[6],P1_filter[6][6],P2_filter[6][6],P3_filter[6][6];
double S1[3][3] = {{0}},S2[3][3] = {{0}},S3[3][3] = {{0}};
@@ -406,7 +443,7 @@ void Track_Asso_Tas::model_filter( std::vector<Trust_Track> *t
double Possibility3=(det_S3>1e-12)?(1.0/sqrt(pow(2*PI,3)*det_S3)*exp(-0.5*d3)):0.0;
//更新航迹
for(size_t i=0;i<trust_track->size();i++)
for(size_t i=0;i<trust_track->size();i++){
if((*trust_track)[i].Track_Index == tas_track_idx)
{
@@ -450,6 +487,8 @@ void Track_Asso_Tas::model_filter( std::vector<Trust_Track> *t
(*trust_track)[i].GNSS_time = point_process[point_index-1].GNSS_time;
//更新关联上的点迹信息
(*trust_track)[i].point_id=point_process[point_index-1].point_id;
(*trust_track)[i].circle_id=point_process[point_index-1].circle_id;
(*trust_track)[i].range_point=point_process[point_index-1].range;
(*trust_track)[i].azi_point=point_process[point_index-1].azimuth/PI*180;
(*trust_track)[i].elev_point= asin(point_process[point_index-1].height/point_process[point_index-1].range)/PI*180;
@@ -472,22 +511,33 @@ void Track_Asso_Tas::model_filter( std::vector<Trust_Track> *t
//更新高度
track_hight_update(tas_track_idx,point_index,trust_track);
}
}
for(auto& info : associated_info)
{
#ifdef RDP_RECORD_ENABLE
rec_asso.push_back(info);
if (rec_asso.size() >= RDP_RECORD_ASSO_CHUNK_ROWS)
{
RDP_RECORD_ASSO_INFOS(rec_asso);
}
#endif
}
}
//未关联上,航迹外推
else
{
//RDP_LOG << "unassociated point for track index " << tas_track_idx << std::endl;
//RDP_LOG_DEBUG("unassociated point for track index {}", tas_track_idx);
for(size_t i=0;i<trust_track->size();i++){
if((*trust_track)[i].Track_Index == tas_track_idx)
{
if((*trust_track)[i].Extrapolate_round >= MAX_EXTRAPOLATE_NUM_TAS)
if((*trust_track)[i].Extrapolate_round >= rdp_parameter.extrapolate_max_tas)
{
double delta_T = (latest_timestamp - T_track)/1000.0;
if (delta_T <= 0)
continue;
double F[6][6], Q1[6][6], Q2[6][6], Q3[6][6];
IMM_F_Q_gen( v_track, delta_T, F, Q1, Q2, Q3,Work_Parameter);
IMM_F_Q_gen( v_track, delta_T, F, Q1, Q2, Q3);
double X1_pred[6],X2_pred[6],X3_pred[6],P1_pred[6][6],P2_pred[6][6],P3_pred[6][6];
kalman Kalman;
@@ -515,6 +565,13 @@ void Track_Asso_Tas::model_filter( std::vector<Trust_Track> *t
}
}
#ifdef RDP_RECORD_ENABLE
//函数返回前把剩余未入队的关联信息 flush 掉(宏内部 std::move,调用后 rec_asso 为空)
if (!rec_asso.empty())
{
RDP_RECORD_ASSO_INFOS(rec_asso);
}
#endif
}
void Track_Asso_Tas::model_output(std::vector<Trust_Track> *trust_track, int tas_track_idx )
@@ -589,7 +646,7 @@ void Track_Asso_Tas::model_output(std::vector<Trust_Track> *trust_track, int
}
// 产生 F Q 矩阵
void Track_Asso_Tas::IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6],struct RadarPara Work_Parameter)
void Track_Asso_Tas::IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6])
{
//F
@@ -617,7 +674,7 @@ void Track_Asso_Tas::IMM_F_Q_gen(double v_track, double delta_T,double F[6][6],
//模型1
//Q
double q1=Work_Parameter.Model1_Q_slow;
double q1=rdp_parameter.model_1_Q_slow;
double q111=1/(2*pow(alpha,5))*(1-exp(-2*alpha*delta_T)+2*alpha*delta_T+2*pow(alpha,3)*pow(delta_T,3)/3-2*pow(alpha,2)*pow(delta_T,2)-4*alpha*delta_T*exp(-alpha*delta_T));
double q121=1/(2*pow(alpha,4))*(exp(-2*alpha*delta_T)+1-2*exp(-alpha*delta_T)+2*alpha*delta_T*exp(-alpha*delta_T)-2*alpha*delta_T+pow(alpha,2)*pow(delta_T,2));
double q131=1/(2*pow(alpha,3))*(1-exp(-2*alpha*delta_T)-2*alpha*delta_T*exp(-alpha*delta_T));
@@ -638,15 +695,15 @@ void Track_Asso_Tas::IMM_F_Q_gen(double v_track, double delta_T,double F[6][6],
double q2;
if(v_track<=5)
{
q2=Work_Parameter.Model2_Q_slow/20;
q2=rdp_parameter.model_2_Q_slow/20;
}
else if(v_track<=100&&v_track>50)
{
q2=10.0*Work_Parameter.Model2_Q_fast;
q2=10.0*rdp_parameter.model_2_Q_fast;
}
else
{
q2=Work_Parameter.Model2_Q_fast;
q2=rdp_parameter.model_2_Q_fast;
}
double q112=1/(2*pow(alpha,5))*(1-exp(-2*alpha*delta_T)+2*alpha*delta_T+2*pow(alpha,3)*pow(delta_T,3)/3-2*pow(alpha,2)*pow(delta_T,2)-4*alpha*delta_T*exp(-alpha*delta_T));
@@ -670,15 +727,15 @@ Q2[5][3]=q2*q132; Q2[5][4]=q2*q232; Q2[5][5]=q2*q332;
double q3;
if(v_track<=5)
{
q3=Work_Parameter.Model3_Q_slow/20;
q3=rdp_parameter.model_3_Q_slow/20;
}
else if(v_track<=100&&v_track>50)
{
q3=10.0*Work_Parameter.Model3_Q_fast;
q3=10.0*rdp_parameter.model_3_Q_fast;
}
else
{
q3=Work_Parameter.Model3_Q_fast;
q3=rdp_parameter.model_3_Q_fast;
}
double q113=1/(2*pow(alpha,5))*(1-exp(-2*alpha*delta_T)+2*alpha*delta_T+2*pow(alpha,3)*pow(delta_T,3)/3-2*pow(alpha,2)*pow(delta_T,2)-4*alpha*delta_T*exp(-alpha*delta_T));
double q123=1/(2*pow(alpha,4))*(exp(-2*alpha*delta_T)+1-2*exp(-alpha*delta_T)+2*alpha*delta_T*exp(-alpha*delta_T)-2*alpha*delta_T+pow(alpha,2)*pow(delta_T,2));
@@ -705,11 +762,10 @@ double X2[6], double P2[6][6],
double X3[6], double P3[6][6],
double Z[3], double prt,double freq_ind,
double delta_T,
double *d1,double *d2,double *d3,
struct RadarPara Work_Parameter)
double *d1,double *d2,double *d3)
{
double F[6][6], Q1[6][6], Q2[6][6], Q3[6][6];
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3,Work_Parameter);
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3);
kalman Kalman;
// *d1=Kalman.d_cal_with_doppler(F,Q1,Z,X1,P1,v_point,prt,freq_ind);
@@ -735,19 +791,19 @@ void Track_Asso_Tas::track_hight_update(int tas_
int height_win_length;
if((*trust_track)[i].range_point <= 1000)
{
height_win_length = H_F_WIN_LEN+2;
height_win_length = rdp_parameter.height_smooth_win+6;
}
else if((*trust_track)[i].range_point <= 2000 && (*trust_track)[i].range_point > 1000)
{
height_win_length = H_F_WIN_LEN+3;
height_win_length = rdp_parameter.height_smooth_win+8;
}
else if((*trust_track)[i].range_point <= 4000 && (*trust_track)[i].range_point > 2000)
{
height_win_length = H_F_WIN_LEN+4;
height_win_length = rdp_parameter.height_smooth_win+10;
}
else
{
height_win_length = H_F_WIN_LEN+6;
height_win_length = rdp_parameter.height_smooth_win+12;
}
double sum=0;
+3 -6
View File
@@ -15,7 +15,6 @@ public:
std::vector<Trust_Track> *trust_track, //航迹文件
struct Track Trust_Track_Output[MAX_TRACK_NUM][10], //更新航迹信息
int *Trust_track_num_Output, //更新航迹数
struct RadarPara Work_Parameter, //工作参数
int tas_track_idx,
long long latest_timestamp //最新时间戳
);
@@ -31,7 +30,6 @@ private:
void model_interaction(std::vector<Trust_Track> *trust_track, int tas_track_idx); //模型交互
void model_filter(std::vector<Trust_Track> *trust_track, //滤波
struct RadarPara Work_Parameter,
int tas_track_idx,
long long latest_timestamp //最新时间戳
);
@@ -44,15 +42,14 @@ private:
double X3[6], double P3[6][6],
double Z[3], double prt,double freq_ind,
double delta_T,
double *d1,double *d2,double *d3,
struct RadarPara Work_Parameter);
double *d1,double *d2,double *d3);
void IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6],struct RadarPara Work_Parameter);
void IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6]);
double Pt[3][3]; //模型转移概率
//高度维更新
void track_hight_update(int tas_track_index, //更新的航迹号
void track_hight_update(int tas_track_idx, //更新的航迹号
int asso_point_index, //点迹号
std::vector<Trust_Track> *trust_track //航迹
);
+5 -2
View File
@@ -16,17 +16,20 @@ void Track_Die::track_die_process( std::vector <Trust_Track> *trust_track,
{
bool die_flag = false;
//手动删除
if((*Iter).manual_delete_flag == 1)
{
die_flag = true;
}
if((*Iter).Track_Mode == 0 && (*Iter).primary_target_flag == 0 && (*Iter).Extrapolate_round > TRACK_DIE_ROUND)
//非重点航迹自动消亡
if((*Iter).Track_Mode == 0 && (*Iter).primary_target_flag == 0 && (*Iter).Quality < rdp_parameter.track_die_quality)
{
die_flag = true;
}
if((*Iter).Track_Mode == 0 && (*Iter).primary_target_flag == 1 && (*Iter).Extrapolate_round > TRACK_DIE_ROUND_PRIMARY)
//重点航迹自动消亡
if((*Iter).Track_Mode == 0 && (*Iter).primary_target_flag == 1 && (*Iter).Extrapolate_round > rdp_parameter.track_die_round_pramary)
{
die_flag = true;
}
+2 -2
View File
@@ -24,14 +24,14 @@ void Track_Die_Tas::track_die_process_tas( std::vector <Trust_Track> *trust_trac
die_flag = true;
}
if((*Iter).Track_Mode == 1 && (*Iter).Extrapolate_round > TRACK_DIE_ROUND_TAS)
if((*Iter).Track_Mode == 1 && (*Iter).Extrapolate_round > rdp_parameter.track_die_round_tas)
{
die_flag = true;
}
if(die_flag)
{
// RDP_LOG << "remove tas target :" << (*Iter).Track_Index << std::endl;
// RDP_LOG_DEBUG("remove tas target :{}", (*Iter).Track_Index);
//输出消亡信息
*Track_die_num_Output=*Track_die_num_Output+1;
Track_die_Index_Output[*Track_die_num_Output-1]=(*Iter).Track_Index;
@@ -29,18 +29,19 @@ int Track_Ind_Mangement ::track_ind_get( std::vector<Trust_Track> *trust_track
else
{
//建立航迹号列表
int List[MAX_TRACK_INDEX]={0};
const int track_index_max = rdp_parameter.track_index_max;
std::vector<int> List(static_cast<std::size_t>(track_index_max), 0);
for (size_t i=0;i<trust_track->size();i++)
{
int ti = (*trust_track)[i].Track_Index;
if (ti >= 1 && ti <= MAX_TRACK_INDEX)
if (ti >= 1 && ti <= track_index_max)
List[ti-1]=1;
}
//分配航迹号
if(lastest_index<MAX_TRACK_INDEX)
if(lastest_index<track_index_max)
{
for (int i=lastest_index+1;i<=MAX_TRACK_INDEX;i++)
for (int i=lastest_index+1;i<=track_index_max;i++)
{
if(List[i-1]==0)
{
@@ -49,7 +50,7 @@ int Track_Ind_Mangement ::track_ind_get( std::vector<Trust_Track> *trust_track
}
}
for (int i=1;i<=MAX_TRACK_INDEX;i++)
for (int i=1;i<=track_index_max;i++)
{
if(List[i-1]==0)
{
@@ -59,9 +60,9 @@ int Track_Ind_Mangement ::track_ind_get( std::vector<Trust_Track> *trust_track
}
}
else if(lastest_index == MAX_TRACK_INDEX)
else if(lastest_index == track_index_max)
{
for (int i=1;i<=MAX_TRACK_INDEX;i++)
for (int i=1;i<=track_index_max;i++)
{
if(List[i-1]==0)
{
+174 -42
View File
@@ -7,15 +7,13 @@
#include <iostream>
#include<iomanip>
#include "rdp_record.h"
#include "rdp_record_entity.h"
using namespace std;
int Track_Init::track_init_process_logic( std::vector <PointRecv> *point_recv, //输入点迹
std::vector<Trust_Track> *trust_track, //可靠航迹
std::vector <std::vector<Temp_track>> *temp_track,
struct Track Trust_Track_Output[MAX_TRACK_NUM][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter
int *Trust_track_num_Output
)
{
@@ -36,10 +34,10 @@ int Track_Init::track_init_process_logic( std::vector <PointRecv>
if(point_process.size()>0)
{
//点迹与临时航迹关联
point_temp_track_asso(temp_track,Work_Parameter);
point_temp_track_asso(temp_track);
//点迹与航迹头关联
point_track_head_asso(temp_track,Work_Parameter);
point_track_head_asso(temp_track);
//删除关联上的点迹
std::vector <PointRecv>::iterator Iter;
@@ -107,28 +105,30 @@ int Track_Init::track_init_process_logic( std::vector <PointRecv>
std::vector<PointRecv>().swap(point_process);
std::vector<PointRecv>().swap((*point_recv));
#ifdef RDP_RECORD_ENABLE
//记录临时航迹
std::vector<Temp_track> rec_temp;
for(auto& vec_t : *temp_track){
for(auto& t : vec_t){
rec_temp.push_back(t);
}
}
RDP_RECORD_TEMP_TRACKS(rec_temp);
#endif
//临时航迹满足起始长度 转为可靠航迹
tmp_track_to_trust_track(trust_track,temp_track,Trust_Track_Output, Trust_track_num_Output,Work_Parameter);
tmp_track_to_trust_track(trust_track,temp_track,Trust_Track_Output, Trust_track_num_Output);
}
//消亡临时航迹
tmp_track_die(temp_track);
#ifdef RDP_RECORD_ENABLE
//记录临时航迹
for(auto& vec_t : *temp_track){
for(auto& t : vec_t){
RDP_RECORD(RdpRecTempTrack(t));
}
}
#endif
return 0;
}
void Track_Init::point_temp_track_asso(std::vector <std::vector<Temp_track>> *temp_track,
struct RadarPara Work_Parameter)
void Track_Init::point_temp_track_asso(std::vector <std::vector<Temp_track>> *temp_track)
{
//关联信息
@@ -189,7 +189,7 @@ void Track_Init::point_temp_track_asso(std::vector <std::vector<Temp_track>>
double y2=Z[0]*sin(Z[1]);
double alpha = alpha_cal_track_init(x0,y0,x1,y1,x2,y2);
if(d*d<=TRACK_START_THRESHOLD*TRACK_START_THRESHOLD && alpha<ALPHA_START && vr_point*v_temp_track>0)// && fabs(h_point-h_temp_track )<= r_point*SIGMA_E&& abs(vr_point-v_temp_track)/abs(v_temp_track)<0.8
if(d*d<=rdp_parameter.track_init_threshold*rdp_parameter.track_init_threshold && alpha<rdp_parameter.track_init_angle && vr_point*v_temp_track>0)// && fabs(h_point-h_temp_track )<= r_point*rdp_parameter.sigma_pitch&& abs(vr_point-v_temp_track)/abs(v_temp_track)<0.8
{
struct Asso_info asso_info_tmp;
asso_info_tmp.point_idx = static_cast<int>(i)+1;
@@ -253,6 +253,40 @@ void Track_Init::point_temp_track_asso(std::vector <std::vector<Temp_track>>
memcpy(asso_track_info_tmp.X, X, 4*sizeof(double));
memcpy(asso_track_info_tmp.P, P, 4*4*sizeof(double));
(*temp_track)[(*temp_track).size()-1].push_back(asso_track_info_tmp);
//计算航迹质量
auto& temp_track_ref = (*temp_track)[(*temp_track).size()-1];
L = static_cast<int>(temp_track_ref.size());
double snr_tmp = 0;
double d_tmp = 0;
double quality_tmp = 0;
d_tmp = exp(-temp_track_ref[L-1].d);
if(L == 3){
double average_snr = 0;
for(int i=0;i<L;i++)
{
average_snr += temp_track_ref[i].SNR;
}
average_snr /= L;
snr_tmp = 1/(1+exp(-(average_snr - 16) / 1.2));
}else{
snr_tmp = 1/(1+exp(-(temp_track_ref.back().SNR - 16) / 1.2));
}
quality_tmp = d_tmp * rdp_parameter.track_quality_d_weight + snr_tmp * rdp_parameter.track_quality_snr_weight;
quality_tmp /= (rdp_parameter.track_quality_d_weight + rdp_parameter.track_quality_snr_weight);
if(L == 3){
for(int i=0;i<L;i++)
{
temp_track_ref[i].quality = quality_tmp;
}
}else{
temp_track_ref[L-1].quality = temp_track_ref[L-2].quality * rdp_parameter.track_quality_iter_coeff + quality_tmp * (1 - rdp_parameter.track_quality_iter_coeff);
}
}
//temp_track中删除关联上的临时航迹
@@ -278,8 +312,7 @@ void Track_Init::point_temp_track_asso(std::vector <std::vector<Temp_track>>
}
}
void Track_Init::point_track_head_asso( std::vector <std::vector<Temp_track>> *temp_track,
struct RadarPara Work_Parameter)
void Track_Init::point_track_head_asso( std::vector <std::vector<Temp_track>> *temp_track)
{
//关联上的信息
@@ -321,26 +354,26 @@ void Track_Init::point_track_head_asso( std::vector <std::vector<Temp_track>>
double dis = sqrt(pow(x_point-x_track_head,2)+pow(y_point-y_track_head,2));
double vmax;
if(Work_Parameter.work_mode == 0) //近程模式 最大速度减小一点
if(rdp_parameter.radar_work_mode == 0) //近程模式 最大速度减小一点
{
vmax = Work_Parameter.V_MAX;
vmax = rdp_parameter.track_init_velocity_max;
}
else //中远程模式 最大速度正常用
{
vmax = Work_Parameter.V_MAX;
vmax = rdp_parameter.track_init_velocity_max;
}
double delta_T = (T_point - T_track_head)/1000.0;
// if(
// ( (Work_Parameter.work_mode == 0&&( (r_point>=1000 && dis<=vmax*delta_T && dis>=Work_Parameter.V_MIN*delta_T) || (r_point<1000 && dis<=vmax*delta_T/2.0 && dis>=Work_Parameter.V_MIN*delta_T/2.0) ))
// || (Work_Parameter.work_mode != 0&&( (r_point>=2000 && dis<=vmax*delta_T && dis>=Work_Parameter.V_MIN*delta_T) || (r_point<2000 && dis<=vmax*delta_T/3.0 && dis>=Work_Parameter.V_MIN*delta_T/2.0) ))
// ( (rdp_parameter.radar_work_mode == 0&&( (r_point>=1000 && dis<=vmax*delta_T && dis>=rdp_parameter.track_init_velocity_min*delta_T) || (r_point<1000 && dis<=vmax*delta_T/2.0 && dis>=rdp_parameter.track_init_velocity_min*delta_T/2.0) ))
// || (rdp_parameter.radar_work_mode != 0&&( (r_point>=2000 && dis<=vmax*delta_T && dis>=rdp_parameter.track_init_velocity_min*delta_T) || (r_point<2000 && dis<=vmax*delta_T/3.0 && dis>=rdp_parameter.track_init_velocity_min*delta_T/2.0) ))
// )
// && vr_point*v_track_head>0 )
//点航关联条件
if( dis <= vmax* delta_T && dis >= Work_Parameter.V_MIN * delta_T && \
if( dis <= vmax* delta_T && dis >= rdp_parameter.track_init_velocity_min * delta_T && \
vr_point*v_track_head>0 && \
dis >= abs(vr_point) * delta_T && \
dis >= abs(v_track_head) * delta_T
@@ -437,8 +470,7 @@ double Track_Init::alpha_cal_track_init(double x0,double y0,double x1,double y1,
void Track_Init::tmp_track_to_trust_track(std::vector<Trust_Track> *trust_track,
std::vector <std::vector<Temp_track>> *temp_track,
struct Track Trust_Track_Output[MAX_TRACK_NUM][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter)
int *Trust_track_num_Output)
{
std::vector <std::vector<Temp_track>> Track_to_start;
@@ -450,17 +482,64 @@ void Track_Init::tmp_track_to_trust_track(std::vector<Trust_Track> *trust_tra
int L=static_cast<int>((*Iter).size());
double range=(*Iter)[L-1].r;
double azi=(*Iter)[L-1].azi/PI*180;
double quality = (*Iter)[L-1].quality;
//(已停用)1km以内,SNR小于20dB的临时航迹不转为可靠航迹
// bool snr_flag = true;
// for(int p_idx = 0; p_idx < L; p_idx++){
// if((*Iter)[p_idx].r < 1000 && (*Iter)[p_idx].snr < 20){
// snr_flag = false;
// break;
// }
// }
//航迹起批条件判断
bool track_init_flag = true;
if( (L <=10 && L>=Work_Parameter.track_start_point_num)&& track_init_prohibit(range,azi,Work_Parameter)==0) //按长度查找TRUST_TRACK_POINT
//航迹长度大于10点,不起批
if(L > 10){
track_init_flag = false;
}
//航迹长度小于起始点数,不起批
if(L < rdp_parameter.track_init_point_num_min){
track_init_flag = false;
}
//航迹起批禁止区域判断
if(track_init_prohibit(range,azi)!=0)
{
track_init_flag = false;
}
//航迹质量起批条件判断
switch(L){
case 3:
if(quality < rdp_parameter.track_init_quality_L3){
track_init_flag = false;
}
break;
case 4:
if(quality < rdp_parameter.track_init_quality_L4){
track_init_flag = false;
}
break;
case 5:
if(quality < rdp_parameter.track_init_quality_L5){
track_init_flag = false;
}
break;
case 6:
if(quality < rdp_parameter.track_init_quality_L6){
track_init_flag = false;
}
break;
case 7:
if(quality < rdp_parameter.track_init_quality_L7){
track_init_flag = false;
}
break;
default:
if(quality < rdp_parameter.track_init_quality_L7){
track_init_flag = false;
}
break;
}
//TODO: 杂波密度判断
if(track_init_flag)
{
//加入Track_to_start中
Track_to_start.push_back(std::vector <Temp_track> ());
@@ -481,7 +560,7 @@ void Track_Init::tmp_track_to_trust_track(std::vector<Trust_Track> *trust_tra
return;
//2.两两比较Track_to_start中的航迹信息,删除重复的航迹
int L = Work_Parameter.track_start_point_num;
int L = rdp_parameter.track_init_point_num_min;
for (size_t i=0; i+1<Track_to_start.size();i++)
{
if(Track_to_start[i][0].asso_flag!=2)
@@ -532,7 +611,7 @@ void Track_Init::tmp_track_to_trust_track(std::vector<Trust_Track> *trust_tra
for (size_t i=0; i<Track_to_start.size();i++)
{
if (Track_to_start[i].size() < 3) continue;
if(trust_track->size()<MAX_TRACK_INDEX)
if(trust_track->size() < static_cast<std::size_t>(rdp_parameter.track_index_max))
{
int index = track_index_mangement.track_ind_get(trust_track);
if(index>0)
@@ -553,6 +632,9 @@ void Track_Init::tmp_track_to_trust_track(std::vector<Trust_Track> *trust_tra
memcpy(trust_track_tmp.X, X, 6*sizeof(double));
memcpy(trust_track_tmp.P, P, 6*6*sizeof(double));
//计算航迹指纹
trust_track_tmp.Track_fingerprint = computeFingerprint(Track_to_start[i][L-1].r, Track_to_start[i][L-1].azi, Track_to_start[i][L-1].vr, Track_to_start[i][L-1].RCS, Track_to_start[i][L-1].CPI_time);
trust_track_tmp.track_id = Track_to_start[i][L-1].temp_track_id;
trust_track_tmp.point_id = Track_to_start[i][L-1].point_id;
trust_track_tmp.circle_id = Track_to_start[i][L-1].circle_id;
@@ -564,9 +646,10 @@ void Track_Init::tmp_track_to_trust_track(std::vector<Trust_Track> *trust_tra
std::memcpy(trust_track_tmp.range_dim, Track_to_start[i][L-1].range_dim, sizeof(trust_track_tmp.range_dim));
trust_track_tmp.T_track = Track_to_start[i][L-1].CPI_time;
trust_track_tmp.GNSS_time = Track_to_start[i][L-1].GNSS_time;
trust_track_tmp.Unupdated_time = 0;
trust_track_tmp.point_flag=1; //实点
trust_track_tmp.Track_Mode=0; //跟踪模式 TWS 0
trust_track_tmp.Target_Type=UNCONF_TARGET;
trust_track_tmp.Target_Type=0;
trust_track_tmp.Track_Index=index;
trust_track_tmp.Height=Track_to_start[i][L-1].height;
trust_track_tmp.Extrapolate_round=0;
@@ -575,6 +658,9 @@ void Track_Init::tmp_track_to_trust_track(std::vector<Trust_Track> *trust_tra
trust_track_tmp.associate_point_number = 0;
trust_track_tmp.manual_tracking_flag = 0;
trust_track_tmp.point_type = 0;
trust_track_tmp.Quality = Track_to_start[i][L-1].quality;
trust_track_tmp.Point_sum = L;
trust_track_tmp.Valid_point_sum = L;
trust_track_tmp.u[0]=0.3333;
trust_track_tmp.u[1]=0.3333;
trust_track_tmp.u[2]=0.3333;
@@ -611,6 +697,7 @@ void Track_Init::tmp_track_to_trust_track(std::vector<Trust_Track> *trust_tra
Trust_Track_Output[*Trust_track_num_Output-1][j].Amplitude=static_cast<float>(Track_to_start[i][j].amplitude);
Trust_Track_Output[*Trust_track_num_Output-1][j].track_snr = static_cast<float>(Track_to_start[i][j].SNR);
Trust_Track_Output[*Trust_track_num_Output-1][j].track_rcs = static_cast<float>(Track_to_start[i][j].RCS);
Trust_Track_Output[*Trust_track_num_Output-1][j].Quality = static_cast<float>(Track_to_start[i][j].quality * rdp_parameter.track_quality_max);
Trust_Track_Output[*Trust_track_num_Output-1][j].pitch_num = Track_to_start[i][j].pitch_num;
@@ -678,12 +765,12 @@ void Track_Init::tmp_track_die(std::vector <std::vector<Temp_track>> *temp_t
}
};
int Track_Init::track_init_prohibit(double r, double azi, struct RadarPara Work_Parameter)
int Track_Init::track_init_prohibit(double r, double azi)
{
for (int i=0;i<Work_Parameter.track_prohibite_area_num;i++)
for (int i=0;i<rdp_parameter.track_init_prohibite_area_num;i++)
{
if(r<Work_Parameter.R_max_track_prohibited[i] && r>Work_Parameter.R_min_track_prohibited[i] && azi<Work_Parameter.Azimuth_max_track_prohibited[i] && azi>Work_Parameter.Azimuth_min_track_prohibited[i])
if(r<rdp_parameter.track_init_prohibite_area_range_max[i] && r>rdp_parameter.track_init_prohibite_area_range_min[i] && azi<rdp_parameter.track_init_prohibite_area_azimuth_max[i] && azi>rdp_parameter.track_init_prohibite_area_azimuth_min[i])
{
return 1;
}
@@ -693,6 +780,51 @@ int Track_Init::track_init_prohibit(double r, double azi, struct RadarPara
}
uint64_t Track_Init::mix64(uint64_t x) {
x ^= x >> 30; x *= 0xBF58476D1CE4E5B9ULL;
x ^= x >> 27; x *= 0x94D049BB133111EBULL;
x ^= x >> 31;
return x;
}
uint64_t Track_Init::quantize(double v, double scale) {
if (std::isnan(v)) return 0x9E3779B97F4A7C15ULL;
if (std::isinf(v))
return v > 0 ? 0x2C08A6D5E3F1B907ULL : 0xD5E3F1B9072C08A6ULL;
double q = std::round(v * scale); // half away from zero
constexpr double kLim = 9.0e18; // < 2^63,避免转 int64 溢出
if (q > kLim) q = kLim;
if (q < -kLim) q = -kLim;
return static_cast<uint64_t>(static_cast<int64_t>(q));
}
uint64_t Track_Init::bits_of(double v) {
if (std::isnan(v)) return 0x7FF8000000000000ULL;
if (v == 0.0) return 0ULL;
uint64_t b;
std::memcpy(&b, &v, sizeof(b));
return b;
}
uint16_t Track_Init::fold16(uint64_t h) {
h ^= h >> 32;
h ^= h >> 16;
return static_cast<uint16_t>(h);
}
uint16_t Track_Init::computeFingerprint(double r, double azi, double vr, double RCS,
long long CPI_time)
{
uint64_t h = kSeed;
h = mix64(h ^ quantize(r, 100.0)); // 距离:保留 0.01 m
h = mix64(h ^ quantize(azi, 1000.0)); // 方位:保留 0.001°
h = mix64(h ^ quantize(vr, 100.0)); // 径向速度:保留 0.01 m/s
h = mix64(h ^ quantize(RCS, 10.0)); // RCS:保留 0.1 dBsm
h = mix64(h ^ static_cast<uint64_t>(CPI_time));
return fold16(h);
}
Track_Init::Track_Init()
{
+12 -9
View File
@@ -18,8 +18,7 @@ public:
std::vector<Trust_Track> *trust_track, //可靠航迹
std::vector <std::vector<Temp_track>> *temp_track,
struct Track Trust_Track_Output[MAX_TRACK_NUM][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter
int *Trust_track_num_Output
);
Track_Init();
@@ -31,23 +30,27 @@ private:
std::vector<PointRecv> point_process; //要处理的点迹
void point_temp_track_asso(std::vector <std::vector<Temp_track>> *temp_track,
struct RadarPara Work_Parameter); //临时航迹与点迹关联
void point_temp_track_asso(std::vector <std::vector<Temp_track>> *temp_track); //临时航迹与点迹关联
void point_track_head_asso( std::vector <std::vector<Temp_track>> *temp_track,
struct RadarPara Work_Parameter); //航迹头与点迹关联
void point_track_head_asso( std::vector <std::vector<Temp_track>> *temp_track); //航迹头与点迹关联
void tmp_track_to_trust_track( std::vector <Trust_Track> *trust_track, //临时航迹转可靠航迹
std::vector <std::vector<Temp_track>> *temp_track,
struct Track Trust_Track_Output[MAX_TRACK_NUM][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter);
int *Trust_track_num_Output);
void tmp_track_die(std::vector <std::vector<Temp_track>> *temp_track);//临时航迹消亡
double alpha_cal_track_init(double x0,double y0,double x1,double y1,double x2,double y2); //计算夹角
int track_init_prohibit(double r, double azi, struct RadarPara Work_Parameter); //判断目标是否位于航迹起始屏蔽区 是返回1 否返回0
int track_init_prohibit(double r, double azi); //判断目标是否位于航迹起始屏蔽区 是返回1 否返回0
const uint64_t kSeed = 0x243F6A8885A308D3ULL;
uint64_t mix64(uint64_t x); // splitmix64 混合函数:对任意 64bit 输入产生雪崩均匀的 64bit 输出
uint64_t quantize(double v, double scale); // 量化:把 double 映射到整数域,可容忍 ±0.5/scale 的浮点噪声
uint64_t bits_of(double v); // bit-exact:直接使用 IEEE754 位模式(-0.0 归一为 +0.0,NaN 归一)
uint16_t fold16(uint64_t h); // 把 64bit 折叠成 16bit,充分使用全部熵
uint16_t computeFingerprint(double r, double azi, double vr, double RCS, long long CPI_time); //计算航迹指纹
Track_Ind_Mangement track_index_mangement;
@@ -13,7 +13,6 @@ int Track_Init_Direct_Tracking::track_init_process_logic( std::vector<PointRecv>
std::vector <std::vector<Temp_track>> *temp_track,
struct Track Trust_Track_Output[MAX_TRACK_NUM][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter,
int track_ID
)
{
@@ -22,8 +21,7 @@ int Track_Init_Direct_Tracking::track_init_process_logic( std::vector<PointRecv>
}
void Track_Init_Direct_Tracking::point_temp_track_asso(std::vector <std::vector<Temp_track>> *temp_track,
struct RadarPara Work_Parameter) //临时航迹与点迹关联
void Track_Init_Direct_Tracking::point_temp_track_asso(std::vector <std::vector<Temp_track>> *temp_track) //临时航迹与点迹关联
{
//关联信息
struct Asso_info
@@ -82,7 +80,7 @@ void Track_Init_Direct_Tracking::point_temp_track_asso(std::vector <std::vector<
double y2=Z[0]*sin(Z[1]);
double alpha = alpha_cal_track_init(x0,y0,x1,y1,x2,y2);
if(d*d<=TRACK_START_THRESHOLD*TRACK_START_THRESHOLD && alpha<ALPHA_START && vr_point*v_temp_track>0)// && fabs(h_point-h_temp_track )<= r_point*SIGMA_E&& abs(vr_point-v_temp_track)/abs(v_temp_track)<0.8
if(d*d<=rdp_parameter.track_init_threshold*rdp_parameter.track_init_threshold && alpha<rdp_parameter.track_init_angle && vr_point*v_temp_track>0)// && fabs(h_point-h_temp_track )<= r_point*rdp_parameter.sigma_pitch&& abs(vr_point-v_temp_track)/abs(v_temp_track)<0.8
{
struct Asso_info asso_info_tmp;
asso_info_tmp.point_idx = static_cast<int>(i)+1;
@@ -163,8 +161,7 @@ void Track_Init_Direct_Tracking::point_temp_track_asso(std::vector <std::vector<
}
}
void Track_Init_Direct_Tracking::point_track_head_asso( std::vector <std::vector<Temp_track>> *temp_track,
struct RadarPara Work_Parameter) //航迹头与点迹关联
void Track_Init_Direct_Tracking::point_track_head_asso( std::vector <std::vector<Temp_track>> *temp_track) //航迹头与点迹关联
{
//关联上的信息
struct Asso_info
@@ -205,24 +202,24 @@ void Track_Init_Direct_Tracking::point_track_head_asso( std::vector <std::vector
double dis = sqrt(pow(x_point-x_track_head,2)+pow(y_point-y_track_head,2));
double vmax;
if(Work_Parameter.work_mode == 0) //近程模式 最大速度减小一点
if(rdp_parameter.radar_work_mode == 0) //近程模式 最大速度减小一点
{
//vmax = V_MAX/5;
vmax = Work_Parameter.V_MAX;
//vmax = rdp_parameter.track_init_velocity_max/5;
vmax = rdp_parameter.track_init_velocity_max;
}
else //中远程模式 最大速度正常用
{
// vmax = V_MAX;
vmax = Work_Parameter.V_MAX;
// vmax = rdp_parameter.track_init_velocity_max;
vmax = rdp_parameter.track_init_velocity_max;
}
double delta_T = (T_point - T_track_head)/1000.0;
//满足关联条件的点航
if( ( (r_point>=1000 && dis<=vmax*delta_T && dis>=Work_Parameter.V_MIN*delta_T) || (r_point<1000 && dis<=vmax*delta_T/2.0 && dis>=Work_Parameter.V_MIN*delta_T/2.0) )
&& vr_point*v_track_head>0 )//&& fabs(vr_point-v_track_head)/fabs(v_track_head)<0.2 && fabs(h_point-h_track_head)<=r_point*SIGMA_E
if( ( (r_point>=1000 && dis<=vmax*delta_T && dis>=rdp_parameter.track_init_velocity_min*delta_T) || (r_point<1000 && dis<=vmax*delta_T/2.0 && dis>=rdp_parameter.track_init_velocity_min*delta_T/2.0) )
&& vr_point*v_track_head>0 )//&& fabs(vr_point-v_track_head)/fabs(v_track_head)<0.2 && fabs(h_point-h_track_head)<=r_point*rdp_parameter.sigma_pitch
{
struct Asso_info asso_info_tmp;
asso_info_tmp.point_idx = static_cast<int>(i)+1;
@@ -299,7 +296,6 @@ void Track_Init_Direct_Tracking::tmp_track_to_trust_track( std::vector<Trust_Tr
std::vector <std::vector<Temp_track>> *temp_track,
struct Track Trust_Track_Output[MAX_TRACK_NUM][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter,
int track_ID)
{
//temp_track中的临时航迹转为可靠航迹
@@ -314,7 +310,7 @@ void Track_Init_Direct_Tracking::tmp_track_to_trust_track( std::vector<Trust_Tr
}
double range=(*Iter)[L-1].r;
double azi=(*Iter)[L-1].azi/PI*180;
if( L==Work_Parameter.track_start_point_num) //按长度查找TRUST_TRACK_POINT
if( L==rdp_parameter.track_init_point_num_min) //按长度查找TRUST_TRACK_POINT
{
//加入本地航迹文件
@@ -338,7 +334,7 @@ void Track_Init_Direct_Tracking::tmp_track_to_trust_track( std::vector<Trust_Tr
trust_track_tmp.T_track = (*Iter)[L-1].CPI_time;
trust_track_tmp.point_flag=1; //实点
trust_track_tmp.Track_Mode=1; //跟踪模式 TWS 0
trust_track_tmp.Target_Type=UNCONF_TARGET;
trust_track_tmp.Target_Type=0;
trust_track_tmp.Height=(*Iter)[L-1].height;
trust_track_tmp.Extrapolate_round=0;
trust_track_tmp.approach_flag = -1;
@@ -14,7 +14,6 @@ public:
std::vector <std::vector<Temp_track>> *temp_track,
struct Track Trust_Track_Output[MAX_TRACK_NUM][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter,
int track_ID
);
@@ -25,17 +24,14 @@ private:
std::vector<PointRecv> point_process; //要处理的点迹
void point_temp_track_asso(std::vector <std::vector<Temp_track>> *temp_track,
struct RadarPara Work_Parameter); //临时航迹与点迹关联
void point_temp_track_asso(std::vector <std::vector<Temp_track>> *temp_track); //临时航迹与点迹关联
void point_track_head_asso( std::vector <std::vector<Temp_track>> *temp_track,
struct RadarPara Work_Parameter); //航迹头与点迹关联
void point_track_head_asso( std::vector <std::vector<Temp_track>> *temp_track); //航迹头与点迹关联
void tmp_track_to_trust_track( std::vector<Trust_Track> *trust_track, //临时航迹转可靠航迹
std::vector <std::vector<Temp_track>> *temp_track,
struct Track Trust_Track_Output[MAX_TRACK_NUM][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter,
int track_ID
);
+116 -89
View File
@@ -20,7 +20,7 @@
1. **时间主线**:所有时间戳为 64 位毫秒(CPI/GNSS),参与运算时除 1000 转秒;ΔT = 点迹时间 − 航迹时间,ΔT 的正负决定"有效更新 / 只消费不更新 / 跳过"。
2. **状态主线**:每条可靠航迹维护一套 IMM(3 个 Singer 模型)状态 [x, vx, ax, y, vy, ay],x/y 两轴解耦,量测为「距离-方位-径向速度」三维 EKF。
3. **生命周期主线**:可靠航迹靠"外推轮数"消亡(判据为 `>`:TWS 普通航迹 6 圈、重点目标 10 圈,TAS 相扫 10 / 机扫 96 圈);临时航迹靠"缓存圈数 buff_round"消亡。
3. **生命周期主线**:可靠航迹的消亡分两种判据——**TWS 普通航迹自 V1.5.8 起按航迹质量**(`Quality < track_die_quality`,配置项 0.3),**重点目标按外推轮数**(判据 `> 10`)、**TAS 按外推轮数**(判据 `> 阈值`:相扫 20 / 机扫 96 圈);临时航迹靠"缓存圈数 buff_round"消亡。
---
@@ -42,13 +42,13 @@ flowchart TD
A3 --> OUT1["输出更新航迹 + 起批航迹历史点 + 消亡批号"]
M3 --> B1["TAS 凝聚(单帧)"]
B1 --> B2["仅对指定批号航迹做关联/EKF 或外推"]
B1 --> B2["按航迹指纹定位航迹,仅对它做关联/EKF 或外推"]
B2 --> B3["TAS 超时消亡"]
B3 --> OUT2["输出该批号航迹"]
B3 --> OUT2["输出该航迹"]
OUT1 --> BC["Beam_Ctrl 波束控制"]
OUT2 --> BC
BC --> OUT3["TrackingBeam:距离/方位/俯仰/批号"]
BC --> OUT3["TrackingBeam:距离/方位/俯仰/航迹指纹"]
```
---
@@ -74,20 +74,23 @@ flowchart TD
flowchart TD
A["收到一帧点迹"] --> B{"point_type ?"}
B -->|"0 = TWS"| C["写入 TWS 缓存"]
C --> D{"方位波位号 Beam_index_aiz == 0 ?"}
C --> D{"判圈条件成立(由配置 scan_mode 选择)?<br/>scan_mode=0 相扫: Beam_index_aiz <= last_beam_num<br/>scan_mode=1 机扫: Beam_index_aiz == 0"}
D -->|"是"| E["return 1:满一圈,执行 TWS 航迹处理"]
D -->|"否"| F["return 2:未满圈,仅做滑窗凝聚"]
B -->|"1 = TAS"| G["写入 TAS 缓存<br/>记录 TAS 批号与本帧时间戳"]
B -->|"1 = TAS"| G["写入 TAS 缓存<br/>记录 TAS 航迹指纹与本帧时间戳"]
G --> H["return 3:TAS 处理"]
B -->|"其它"| I["return 0:无效数据,丢弃"]
```
**要点**
- 判圈条件由"波位号由大变小"改为 **波位号为 0**(7d5a56f),避免机扫转台反转时误判一圈完成(代码中保留了旧判断的注释)。
- 波位号 0 是每圈的起始波位,因此**每圈恰好触发一次 model=1**。
- 清空(`track_clear_all`)后圈状态复位,下一次调用必然判为"满一圈"。
- 判圈条件由配置项 `[radar_params] scan_mode` **运行时**选择(0 相扫 / 1 机扫;加载器校验仅允许 0/1),替代原先 `#ifdef PHASE_SCANNING` 的编译期分支:
- **scan_mode=0(相扫)**:比较前后两帧方位波位号,当前帧 `Beam_index_aiz <= last_beam_num` 判为满一圈;`last_beam_num` 参与判断,`track_clear_all()` 复位为 INT_MAX,故**清空后/首帧即判满一圈**;
- **scan_mode=1(机扫)**:判 **波位号为 0**(7d5a56f 起),避免转台反转时波位号变小导致误判一圈完成(代码中保留了旧判断的注释);该模式下 `last_beam_num` 仍被更新但不参与判断。
- 两种模式下 `last_beam_num` 每帧都会更新,仅判满圈时 `circle_id++` 并返回 1;判圈行为不再由构建期区分(机扫/相扫已合并为同一份构建),仅由配置项 `scan_mode` 决定(见 README 4.2)。
- 机扫下波位号 0 是每圈的起始波位,因此**每圈恰好触发一次 model=1**。
- TAS 数据不参与判圈,**每来一帧就处理一次**(TAS 是调度的跟踪波位,本身就是单目标 CPI)。
- TAS 帧携带的是**航迹指纹**(`DataRev::TAS_track_index`,字段名沿用,V1.5.9 起语义为指纹),`track_process(model=3)` 先用它在航迹表中定位航迹,再进入 TAS 关联(见 7.1、7.2 与 README 9.12)。
### 2.3 调度:model 与动作的对应
@@ -176,7 +179,7 @@ flowchart TD
J --> L{"还有候选?"}
K --> L
L -- 有 --> G
L -- 无 --> M{"未关联航迹(point_flag==0 且非手动TAS):<br/>Extrapolate_round ≤ MAX_EXTRAPOLATE_NUM ?"}
L -- 无 --> M{"未关联航迹(point_flag==0 且非手动TAS):<br/>Extrapolate_round ≤ extrapolate_max ?"}
M -- 是 --> M1["按数据率 Singer 预测一帧<br/>推进航迹时间,Track_Update_Flag=1"]
M -- 否 --> M2["不预测、不推进时间<br/>仅 Extrapolate_round+1,Track_Update_Flag=0"]
M1 --> N["只输出 Track_Update_Flag==1 的航迹行并清零该标志<br/>(其后由 track_init 追加起批历史点)"]
@@ -185,7 +188,7 @@ flowchart TD
### 4.2 波门(统计距离、高度、距离三类都通过才算候选)
**(1)统计距离波门 thd_d**——按航迹距离分段收紧高机动模型(`ASSO_THORD = 4`):
**(1)统计距离波门 thd_d**——按航迹距离分段收紧高机动模型(`associate_threshold`,样例配置 4):
| 航迹距离 r_track | 模型 1 / 2 | 模型 3 |
|---|---|---|
@@ -193,7 +196,7 @@ flowchart TD
| 500 ~ 1000 m | d² < 16 | d² < 16/100(收紧) |
| ≥ 1000 m | d² < 16 | d² < 16 |
> ⚠ **门限语义**:代码里 d 已经是马氏距离平方,判据又写作 `d*d < ASSO_THORD*ASSO_THORD`,`ASSO_THORD = 4` 时等效于 **d < 4**(而非 √d < 4)。调整 ASSO_THORD 时务必记住这一层"二次方"(该行为在 BUG-20 中被识别,有意保留)。
> ⚠ **门限语义**:代码里 d 已经是马氏距离平方,判据又写作 `d*d < associate_threshold*associate_threshold`,样例配置 4 时等效于 **d < 4**(而非 √d < 4)。调整 `associate_threshold` 时务必记住这一层"二次方"(该行为在 BUG-20 中被识别,有意保留)。
**(2)高度门限 thd_h**(e0bf227 起在 TWS 侧生效)——|h_track − h_point| 按航迹距离分段:
@@ -249,14 +252,14 @@ VT > 300 且 R > 1200 → 拒绝(高速目标给固定 1200 m 上限)
### 4.6 高度平滑
关联实点后把点迹高度压入滑窗,输出高度取最近 w 个点的均值(不足 w 个则全平均):
关联实点后把点迹高度压入滑窗,输出高度取最近 w 个点的均值(不足 w 个则全平均)。窗口按距离分段,TWS/TAS 偏移不同、窗长基数取配置项 `height_smooth_win`(样例配置为相扫 6;机扫需配 3。TAS 偏移于 6284f3f 由 +2/+3/+4/+6 改为 +6/+8/+10/+12):
| 点迹距离 | 窗口长度 w |
|---|---|
| ≤ 1000 m | 5 |
| 1000 ~ 2000 m | 6 |
| 2000 ~ 4000 m | 7 |
| > 4000 m | 9 |
| 点迹距离 | TWS 窗口(相扫/机扫) | TAS 窗口(相扫/机扫) |
|---|---|---|
| ≤ 1000 m | 8 / 5 | 12 / 9 |
| 1000 ~ 2000 m | 9 / 6 | 14 / 11 |
| 2000 ~ 4000 m | 10 / 7 | 16 / 13 |
| > 4000 m | 12 / 9 | 18 / 15 |
> 距离越远俯仰测量跳变越大,窗口随之放长;仅 TWS/TAS 关联到实点时才刷新,外推补点不改变高度。
@@ -264,9 +267,9 @@ VT > 300 且 R > 1200 → 拒绝(高速目标给固定 1200 m 上限)
对 `point_flag == 0` 且 `manual_tracking_flag == 0` 的航迹(含 ΔT≤0 被消费的情形),按 `Extrapolate_round` 分两支(50e0b5c):
**(1)未到上限**:`Extrapolate_round <= MAX_EXTRAPOLATE_NUM`(机扫/相扫均为 3)时照常外推:
**(1)未到上限**:`Extrapolate_round <= extrapolate_max`(配置项,默认 3)时照常外推:
- 推算步长取运行时数据率:`work_mode` 0/1/2 → `DATA_RATE_SHORT / MIDDLE / FAR`(秒);
- 推算步长取配置项 `data_rate_tws`(秒;原实现按 `work_mode` 在 `DATA_RATE_SHORT/MIDDLE/FAR` 三档中三选一,改造后统一为单字段);
- 用各子模型的 Singer F、Q 推进一帧(**不做量测更新**);
- `T_track`、`GNSS_time` 各推进 ΔT×1000 毫秒;
- `Extrapolate_round += 1`,`point_flag = 0`(补点/虚点),`Track_Update_Flag = 1` → 本圈作为补点行输出。
@@ -277,9 +280,11 @@ VT > 300 且 R > 1200 → 拒绝(高速目标给固定 1200 m 上限)
**输出门控(50e0b5c)**:TWS 输出航迹的循环改为「`Track_Update_Flag == 1` 才输出,输出后清零」,即每圈只输出**本圈关联到实点或成功外推**的航迹;此前是每圈把航迹表中全部可靠航迹各输出一行。门控只判更新标志、不判 `manual_tracking_flag`,因此手动转 TAS 的航迹若本圈在 TWS 侧关联到实点,仍会经 TWS 通道输出。
> 由于外推上限(3)小于 TWS 消亡轮数(6),普通航迹在停止预测后还会**连续 3 圈不向宿主输出**任何航迹行(`Extrapolate_round` 依次走到 5、6、7,第 7 圈在 `track_die` 中消亡),只在最后输出一次消亡批号。
> **普通 TWS 航迹的消亡改由质量决定(V1.5.8)**:`track_die.cpp` 对 `Track_Mode==0 && primary_target_flag==0` 的航迹改用 `Quality < track_die_quality`(配置项 0.3)判消亡,因此外推超限后航迹仍会驻留,直到质量衰减到门限以下;`Extrapolate_round` 继续每圈 +1 但不再决定普通 TWS 航迹的存活。重点航迹(10 圈)与 TAS(相扫 20 / 机扫 96)不变。
>
> **TAS 通道的判据方向相反**(`track_asso_tas.cpp` L484):`Extrapolate_round >= MAX_EXTRAPOLATE_NUM_TAS` 时才做预测、推进航迹时间并置 `Track_Update_Flag = 1`;未达该轮数时只累加圈数(不预测、不输出)。即 TWS 是"先外推、到上限后停",TAS 是"先不预测、到轮数后才开始外推",详见 7.2 与第 10 节。
> 质量在外推环节按未更新时长二次衰减(`Unupdated_time` 累计,ΔT 由 `work_mode` 数据率决定),且该计算位于外推上限判断之外,故上限内、外都会衰减。
>
> **TAS 通道的判据方向相反**(`track_asso_tas.cpp`):`Extrapolate_round >= extrapolate_max_tas`(配置项,样例相扫 5 / 机扫需配 48)时才做预测、推进航迹时间并置 `Track_Update_Flag = 1`;未达该轮数时只累加圈数(不预测、不输出)。即 TWS 是"先外推、到上限后停",TAS 是"先不预测、到轮数后才开始外推",详见 7.2 与第 10 节;TAS 不维护航迹质量。
---
@@ -318,14 +323,14 @@ flowchart TD
```
|r_point − r_末点| ≥ 5 m 距离差硬门限(近距点不参与)
d ≤ 8(代码写作 d² ≤ TRACK_START_THRESHOLD²=64) 3 维 EKF 统计距离波门
α < ALPHA_START = 60° 折角:最近三点连线夹角
d ≤ 8(代码写作 d² ≤ track_init_threshold²=64) 3 维 EKF 统计距离波门
α < track_init_angle = 60° 折角:最近三点连线夹角
v_r,point · v_r,末点 > 0 多普勒同号
```
- d 由 4 维 CV 状态一步预测 + 「距离-方位-径向速度」EKF 得到(过程噪声 0.03²、同样做方位环绕与盲速折叠);
- 折角 α 为向量 D12(前一点→末点)与 D23(末点→新点)的夹角;α 越小越接近直线;
- 门限同样写作 `d² ≤ TRACK_START_THRESHOLD²`,而 d 本身已是马氏距离平方,实际生效为 d ≤ 8(与可靠航迹关联门限的语义一致)。
- 门限同样写作 `d² ≤ track_init_threshold²`,而 d 本身已是马氏距离平方,`track_init_threshold=8` 时实际生效为 d ≤ 8(与可靠航迹关联门限的语义一致)。
**阶段②:点 ↔ 航迹头(两点起始)**
@@ -374,7 +379,7 @@ dis ≥ |v_r,head| · ΔT 同上,用航迹头的多普勒约
| 条件 | 说明 |
|---|---|
| 长度 | `track_start_point_num ≤ L ≤ 10`(接口层校验 3~9,典型 3 或 4) |
| 长度 | `track_init_point_num_min ≤ L ≤ 10`(配置加载器校验 3~9,典型 3 或 4) |
| 位置 | 末点不落在**起批屏蔽区**(距离-方位矩形,最多 30 个,用严格不等号判断) |
| 容量 | 可靠航迹数 < 500 且能分配到一个未占用航迹号 |
| 初始化 | 用**最后三个点**做三点卡尔曼初始化 → 6 维状态;三模型同状态、u=1/3 |
@@ -390,14 +395,16 @@ dis ≥ |v_r,head| · ΔT 同上,用航迹头的多普勒约
| 通道 | 条件(满足其一即消亡) |
|---|---|
| TWS 普通航迹 | `Track_Mode==0` 且 `primary_target_flag==0` 且 `Extrapolate_round > TRACK_DIE_ROUND`(相扫/机扫均为 6) |
| TWS 重点航迹 | `Track_Mode==0` 且 `primary_target_flag==1` 且 `Extrapolate_round > TRACK_DIE_ROUND_PRIMARY`(10) |
| TAS 航迹 | `Track_Mode==1` 且 `Extrapolate_round > TRACK_DIE_ROUND_TAS`(相扫 10 / 机扫 96) |
| TWS 普通航迹 | `Track_Mode==0` 且 `primary_target_flag==0` 且 **`Quality < track_die_quality`(配置项 0.3)** —— V1.5.8(782bff5)起改按航迹质量,不再看外推圈数 |
| TWS 重点航迹 | `Track_Mode==0` 且 `primary_target_flag==1` 且 `Extrapolate_round > track_die_round_pramary`(配置项 10) |
| TAS 航迹 | `Track_Mode==1` 且 `Extrapolate_round > track_die_round_tas`(配置项;样例相扫 20 / 机扫需配 96) |
| 手动删除 | `manual_delete_flag == 1`(各通道单独判定,置位即消亡,由外部删除接口 `track_delete` 置位) |
三个判据都改为 `die_flag` 组合("或"关系),且比较符由 `>=` 改为 `>`(50e0b5c):即允许的外推圈数比旧版各多 1 圈,且**重点航迹与普通航迹走不同门限**。由于每圈未关联只自增 1,判据为 `>` 时实际是"连续 N+1 圈没有实点关联"才消亡:普通 TWS 航迹 7 圈、重点航迹 11 圈、TAS 相扫 11 圈 / 机扫 97 圈。
轮数类判据都改为 `die_flag` 组合("或"关系),且比较符由 `>=` 改为 `>`(50e0b5c):即允许的外推圈数比旧版各多 1 圈,且**重点航迹与普通航迹走不同门限**。由于每圈未关联只自增 1,判据为 `>` 时实际是"连续 N+1 圈没有实点关联"才消亡:重点航迹 11 圈、TAS 相扫 21 圈(阈值于 5d105d2 由 10 提高到 20)/ 机扫 97 圈。
> `primary_target_flag`(重点目标标记)由外部接口 `Data_Process::primary_target_add / primary_target_cancel` 按批号置 1 / 清 0(`data_process.cpp` L268-297);新起批航迹在 `track_init.cpp` 中以 `Trust_Track trust_track_tmp = {}` 零初始化,故恒为 0。该标记目前只影响上表的 TWS 消亡轮数。
> **普通 TWS 航迹的消亡时长(V1.5.8)**:改按质量后不再有固定圈数,存活时长由质量衰减速度决定——未关联期间按 `Unupdated_time` 的二次函数加速衰减(见 9.9 / README 9.11),`Quality` 跌破 `track_die_quality`(配置项 0.3)即消亡;`track_die_round`(配置项 6)已不再被引用。
> `primary_target_flag`(重点目标标记)由外部接口 `Data_Process::primary_target_add / primary_target_cancel` 按批号置 1 / 清 0(`data_process.cpp`);新起批航迹在 `track_init.cpp` 中以 `Trust_Track trust_track_tmp = {}` 零初始化,故恒为 0。该标记在 TWS 上决定走「质量消亡(普通)」还是「10 圈消亡(重点)」。
消亡时把批号写入消亡输出数组并从航迹表删除;**临时航迹消亡不对外输出**(起批失败的中间态对宿主不可见)。
@@ -428,20 +435,21 @@ flowchart TD
| 项 | TWS | TAS |
|---|---|---|
| 凝聚 | 相邻两帧滑窗,近程多速度维约束 | 单帧,恒有速度维约束 |
| 处理对象 | 全部可靠航迹 × 全部点 | **仅指定批号**的这一条航迹 × 全部点 |
| 处理对象 | 全部可靠航迹 × 全部点 | **仅由指纹定位到的那一条航迹** × 全部点(旧版按批号指定) |
| 俯仰/高度门限 | 俯仰门限已注释停用;**高度门限生效**(按距离分段,e0bf227 起) | **高度门限生效**(按距离分段,取值与 TWS 相同) |
| ΔT ≤ 0 | 参与选取但只消费点迹、不更新 | **直接跳过该点**(并记告警日志) |
| ΔT ≤ 0 | 参与选取但只消费点迹、不更新 | **直接跳过该点**(并以 `RDP_LOG_WARN` 记告警日志) |
| 外推时间 | 按 work_mode 数据率固定步长 | 按 `最新时间戳 − 航迹时间` |
| 外推触发轮数 | `Extrapolate_round <= MAX_EXTRAPOLATE_NUM`(3)才预测 | `Extrapolate_round >= MAX_EXTRAPOLATE_NUM_TAS`(相扫 5 / 机扫 48)才预测(方向与 TWS 相反) |
| 输出 | 本圈有更新的航迹(关联到实点或成功外推,输出后清 `Track_Update_Flag`) | 该批号航迹且本圈有更新 |
| 外推触发轮数 | `Extrapolate_round <= extrapolate_max`(配置项 3)才预测 | `Extrapolate_round >= extrapolate_max_tas`(配置项,样例相扫 5 / 机扫需配 48)才预测(方向与 TWS 相反) |
| 输出 | 本圈有更新的航迹(关联到实点或成功外推,输出后清 `Track_Update_Flag`) | 该航迹且本圈有更新;TAS 波束对外下发**航迹指纹**(`Track_fingerprint`),不再下发批号 |
### 7.2 TAS 关联与更新
```mermaid
flowchart TD
A["TAS 本帧点迹 + 指定批号航迹"] --> B{"该批号航迹存在?"}
A["TAS 本帧点迹 + 航迹指纹"] --> B{"按指纹找到航迹?"}
B -- 否 --> Z["直接返回(不做任何处理)"]
B -- 是 --> C["IMM 交互;航迹 point_flag 置 0"]
B -- 是 --> B1["置 manual_tracking_flag=1、Track_Mode=1"]
B1 --> C["IMM 交互;航迹 point_flag 置 0"]
C --> D["逐点:ΔT ≤ 0 → 记告警并跳过"]
D --> E["逐点:三模型统计距离 d1/d2/d3 + 高度门限"]
E --> F{"d² < 16(任一模型)且高度门限通过?"}
@@ -449,9 +457,9 @@ flowchart TD
F -- 否 --> H["丢弃"]
G --> I["取 d_min 最小的一点"]
I --> J["三模型 EKF 更新 → 概率更新 → 融合<br/>刷新时间/点信息/高度平滑"]
J --> K["输出该批号航迹(Track_Update_Flag==1 才输出,输出后清零)"]
J --> K["输出该航迹(Track_Update_Flag==1 才输出,输出后清零)"]
E --> L{"候选表为空?"}
L -- 是 --> L1{"Extrapolate_round ≥ MAX_EXTRAPOLATE_NUM_TAS ?"}
L -- 是 --> L1{"Extrapolate_round ≥ extrapolate_max_tas ?"}
L1 -- 是 --> M["按 ΔT = 最新时间戳 − 航迹时间 外推一帧<br/>(ΔT ≤ 0 则跳过),Track_Update_Flag=1"]
L1 -- 否 --> M2["不预测、不推进时间<br/>仅 Extrapolate_round+1,Track_Update_Flag=0"]
M --> K
@@ -470,7 +478,7 @@ flowchart TD
**TAS 后续处理**:外推不受 work_mode 数据率约束,而是直接推进到"本帧最新时间戳",保证 TAS 航迹时间与波束调度时刻同步。
**TAS 外推轮数门控(50e0b5c)**:与 TWS **方向相反**——只有 `Extrapolate_round >= MAX_EXTRAPOLATE_NUM_TAS`(相扫 5 / 机扫 48)时才做预测、推进航迹时间并置 `Track_Update_Flag = 1`;未达该轮数时只 `Extrapolate_round += 1`、`point_flag = 0`、`Track_Update_Flag = 0`,既不预测也不输出。因此 TAS 航迹在刚丢失点迹时并不会立刻按"最新时间戳"外推:相扫体制下前 5 次未关联不产生任何输出行,第 6 次起才开始外推输出;机扫体制下前 48 次不输出。该不对称仅如实记录(成因不明,不作缺陷判定),连同 TWS/TAS 输出门控一并见第 10 节。
**TAS 外推轮数门控(50e0b5c)**:与 TWS **方向相反**——只有 `Extrapolate_round >= extrapolate_max_tas`(配置项,样例相扫 5 / 机扫需配 48)时才做预测、推进航迹时间并置 `Track_Update_Flag = 1`;未达该轮数时只 `Extrapolate_round += 1`、`point_flag = 0`、`Track_Update_Flag = 0`,既不预测也不输出。因此 TAS 航迹在刚丢失点迹时并不会立刻按"最新时间戳"外推:相扫配置下前 5 次未关联不产生任何输出行,第 6 次起才开始外推输出;机扫配置(48)下前 48 次不输出。该不对称仅如实记录(成因不明,不作缺陷判定),连同 TWS/TAS 输出门控一并见第 10 节。
---
@@ -494,29 +502,31 @@ flowchart TD
- 队列未满(相扫最多 4 个 / 机扫最多 1 个);
- 不在 TAS 禁止区(距离-方位矩形)。
入队动作:写批号、标记非空、`last_track_time = T_track`、**航迹 Track_Mode 置 1**,并**立即输出一次该航迹更新**(通知宿主跟踪状态变化)。
入队动作:写 `Track_Index`(队列内部键,仍是批号)、标记非空、`last_track_time = T_track`、**航迹 Track_Mode 置 1**,并**立即输出一次该航迹更新**(通知宿主跟踪状态变化;V1.5.9 起该行按常规关联输出的完整字段填充,见 README 9.10 与 CHANGELOG `[1.5.9]` 的 eb9f30c)。
**出队条件**:队列中的批号在航迹表中已不存在,或不再处于"手动 + TAS"状态 → 清空该队列项。
> 自动开启/退出 TAS 的逻辑(按距离/速度/高度阈值)已全部注释停用,**只支持手动**。
>
> **指纹与批号的分工(V1.5.9)**:队列、内部关联与出队仍以 `Track_Index` 为键;只有跨 DLL 边界的 `DataRev.TAS_track_index`(输入)与 `TrackingBeam.TAS_track_index`(输出)改用 **16 bit 航迹指纹** `Track_fingerprint`(起批时按末点特征生成,见 README 9.12)。
### 8.2 波束输出调度(关键)
```mermaid
flowchart TD
A["遍历 TAS 队列"] --> B["按批号在航迹表中找到该航迹"]
B --> C{"距上次波束输出 > DATA_RATE_TAS 秒<br/>且 CPI 时间早于已选中的最早 CPI?"}
A["遍历 TAS 队列"] --> B["按队列项 Index(Track_Index,内部键)在航迹表中找到该航迹"]
B --> C{"距上次波束输出 > data_rate_tas 秒<br/>且 CPI 时间早于已选中的最早 CPI?"}
C -- 否 --> A
C -- 是 --> D["选中该目标(CPI 时间最早者)"]
D --> E["位置外推:ΔT = (最新时间戳 − CPI 时间)/1000<br/>x + vx·ΔT, y + vy·ΔT"]
E --> F["Range = √(xp²+yp²),Azi = atan2(yp,xp)<br/>Elev = asin(Height / Range)"]
F --> G["open_flag=1, type=1, 批号=真实航迹号<br/>更新该目标 last_track_time = 最新时间戳"]
F --> G["open_flag=1, type=default_track_beam_type, TAS_track_index=航迹指纹<br/>更新该目标 last_track_time = 最新时间戳"]
C -.->|"无任何合格目标"| H["open_flag = 0(其余字段保持旧值)"]
```
**要点**
- 门控只用一个条件(e0bf227 修正):目标「距上次波束输出的时间」(`latest_timestamp − last_track_time`)要大于 `DATA_RATE_TAS` 秒(单位是秒,由宿主经参数初始化传入,须为 > 0 的有效值)→ 保证 TAS 目标的数据率**不随 Beam_Ctrl 的调用频率变化**;修正前还要求「航迹时间与最新时间戳之差」也大于该门限,与"取最早 CPI 目标"的语义冲突,会漏掉本应输出的波束;
- 门控只用一个条件(e0bf227 修正):目标「距上次波束输出的时间」(`latest_timestamp − last_track_time`)要大于配置项 `data_rate_tas` 秒(单位是秒,由 TOML 配置文件提供、加载器校验须 > 0)→ 保证 TAS 目标的数据率**不随 Beam_Ctrl 的调用频率变化**;修正前还要求「航迹时间与最新时间戳之差」也大于该门限,与"取最早 CPI 目标"的语义冲突,会漏掉本应输出的波束;
- 多目标时选**队列中 CPI 时间最早**的目标(同时要求其 CPI 时间早于当前已选者),等价于"按最久未更新者优先"的轮转;
- 选中后才按该目标的 CPI 时间算外推步长 `ΔT = (最新时间戳 − CPI 时间)/1000`;
- 原来的"每次调用整队循环移位"已停用;
@@ -526,43 +536,53 @@ flowchart TD
## 9. 参数脉络(决定行为的旋钮)
### 9.1 编译期宏(随扫描体制切换)
> **参数配置方式改造后**:雷达工作参数不再来自 `parameters.h` 编译期宏或宿主下发的 `RadarPara`,而是由 TOML 配置文件(样例 `config/config_tmp.toml`)加载到**全局工作参数对象 `rdp_parameter`**(`struct.h` 中 `extern RDP_parameter rdp_parameter;` 声明、`data_process.cpp` 定义并零初始化),经公开接口 `parameters_reload(配置文件路径)` 在运行期一次性加载(失败返回 -1 且不改变当前参数;严格校验规则与完整参数表见 README 第 10 节)。各处理模块直接访问该全局对象,不再经函数形参逐层传递;全局对象为 DLL 内单实例、非线程安全,配置重载应与处理互斥。下表「样例值」为相扫配置;**机扫需在配置中改**的 5 项(`scan_mode` 及 4 个原按体制区分的参数)已单独标注。
| 宏 | 相扫 | 机扫 | 作用点 |
|---|---|---|---|
| `SIGMA_R / A / E / V` | 10 / 0.02 / 0.2 / 2.0 | 同左 | 量测噪声、EKF 的 R、转换量测协方差 |
| `DOT_COH_RANGE / V / AZI` | 80 / 2 / 6 | 同左 | 凝聚门限(距离 m / 速度 m/s / 方位 °) |
| `R_MIN / R_MAX` | 100 / 100000 | 同左 | 有效量程(凝聚后过滤) |
| `TRACK_START_THRESHOLD` | 8 | 同左 | 起始统计距离波门(d ≤ 8) |
| `ALPHA_START` | 60 | 同左 | 起始折角上限(°) |
| `ASSO_THORD` | 4 | 同左 | 关联波门(判据 d*d < ASSO_THORD²,d 已是马氏距离平方,等效 d < 4) |
| `MAX_EXTRAPOLATE_NUM` | 3 | 同左 | TWS 未关联航迹最大外推轮数(`Extrapolate_round <= 该值` 才预测并输出) |
| `MAX_EXTRAPOLATE_NUM_TAS` | 5 | 48 | TAS 未关联航迹开始外推的轮数(判据 `>=`,方向与 TWS 相反) |
| `TRACK_DIE_ROUND` | 6 | 6 | TWS 普通航迹消亡轮数(判据 `Extrapolate_round > 该值`) |
| `TRACK_DIE_ROUND_PRIMARY` | 10 | 10 | TWS 重点航迹(`primary_target_flag==1`)消亡轮数 |
| `TRACK_DIE_ROUND_TAS` | 10 | 96 | TAS 航迹消亡轮数(判据 `Extrapolate_round > 该值`) |
| `MAX_TRACK_NUM / MAX_TRACK_INDEX` | 500 / 500 | 同左 | 航迹数上限 / 航迹号上限 |
| `TAS_QUEUE_LENGTH / MAX_TAS_NUM` | 4 / 4 | 1 / 1 | TAS 队列长度 / 最大 TAS 目标数 |
| `H_F_WIN_LEN` | 3 | 同左 | 高度平滑窗长基数(实际 +2/+3/+4/+6) |
| `FREQ0` | 9.2 GHz | 同左 | 盲速计算的基准频率 |
| `RDP_LOG_ENABLE / RDP_LOG_DIR` | — | — | 日志落盘开关与目录 |
### 9.1 配置文件参数(`[段] 键`,样例值 = 相扫)
### 9.2 运行时参数(宿主传入,随任务变化)
| 配置键 | 样例值 | 作用点 |
|---|---|---|
| `[radar_params] radar_work_mode` | 0 | 凝聚是否带速度维约束(当前唯一生效点;航迹起始的远近程分支取值相同,属死分支;TWS 外推已改为固定 `data_rate_tws`,不再按模式三选一) |
| `[radar_params] scan_mode` | 0(**机扫配 1**) | **扫描模式 0 相扫 / 1 机扫**(必填,加载器校验仅允许 0/1):决定 `data_preprocess()` 的 TWS 判圈逻辑(0 用波位号比较、1 用波位号==0),替代原编译期 `PHASE_SCANNING`/`MECHANICAL_SCANNING` 分支;机扫/相扫已合并为同一份构建,同一份 DLL 通过该配置切换扫描模式 |
| `[radar_params] freq_0_GHz` | 9.2 | 盲速计算基准频率(原 `FREQ0`) |
| `[radar_params] radar_height` / `radar_system_delay` | 0 / 0 | 输出 z 叠加平台高度 / 输出位置系统延迟补偿(原 `Height` / `Sys_delay`) |
| `[radar_params] data_rate_tws` | 3 | TWS 未关联航迹外推步长(秒;原按 `work_mode` 在 `DATA_RATE_SHORT/MIDDLE/FAR` 中三选一,现统一单字段) |
| `[radar_params] data_rate_tas` | 1 | TAS 波束输出间隔门控(秒,须 > 0) |
| `[cohesion] point_cohesion_range/velocity/azimuth` | 80 / 2 / 6 | 凝聚门限:距离 m / 速度 m/s / 方位 °(原 `DOT_COH_*`) |
| `[cohesion] point_cohesion_range_min/max` | 100 / 100000 | 有效量程(凝聚后过滤;原 `R_MIN/R_MAX`) |
| `[track_associate] sigma_range/azimuth/pitch/velocity` | 10 / 0.02 / 0.2 / 2.0 | 量测噪声、EKF 的 R、转换量测协方差(原 `SIGMA_*`) |
| `[track_associate] associate_threshold` | 4 | 关联波门(判据 `d*d < 该值²`,d 已是马氏距离平方 → 等效 d < 4;原 `ASSO_THORD`) |
| `[track_associate] extrapolate_max` | 3 | TWS 未关联航迹最大外推轮数(`Extrapolate_round <= 该值` 才预测并输出;原 `MAX_EXTRAPOLATE_NUM`) |
| `[track_associate] extrapolate_max_tas` | 5(**机扫配 48**) | TAS 开始外推的轮数(判据 `>=`,方向与 TWS 相反;原 `MAX_EXTRAPOLATE_NUM_TAS`) |
| `[track_associate] height_smooth_win` | 6(**机扫配 3**) | 高度平滑窗长基数:TWS 取 `+2/+3/+4/+6`、TAS 取 `+6/+8/+10/+12`(原 `H_F_WIN_LEN`) |
| `[track_init] track_init_threshold` / `track_init_angle` | 8 / 60 | 起始统计距离波门(d ≤ 8)/ 起始折角上限(°)(原 `TRACK_START_THRESHOLD` / `ALPHA_START`) |
| `[track_init] track_init_point_num_min` | 3 | 起批所需点数(校验 3~9;原 `RadarPara.track_start_point_num`) |
| `[track_init] track_init_quality_L3~L7` | 0.7 / 0.6 / 0.5 / 0.4 / 0.3 | 起批最低质量(按临时航迹长度 3/4/5/6/≥7;原 `TRACK_START_QUALITY_L3~L7`) |
| `[track_init] track_init_velocity_min/max` | 1 / 50 | 航迹头-点关联的速度区间(原 `V_MIN/V_MAX`) |
| `[track_die] track_die_round` | 6 | **已不被引用**:V1.5.8 起 TWS 普通航迹改按质量消亡(保留备用;原 `TRACK_DIE_ROUND`) |
| `[track_die] track_die_round_pramary` | 10 | TWS 重点航迹(`primary_target_flag==1`)消亡轮数(原 `TRACK_DIE_ROUND_PRIMARY`) |
| `[track_die] track_die_round_tas` | 20(**机扫配 96**) | TAS 航迹消亡轮数(判据 `Extrapolate_round > 该值`;原 `TRACK_DIE_ROUND_TAS`) |
| `[track_die] track_die_quality` | 0.3 | TWS 普通航迹质量消亡门限(`Quality < 该值`;原 `TRACK_DIE_QUALITY`) |
| `[track_quality] track_quality_*` | 100 / 0 / 0.5 / 0.3 / 0.2 / 0.8 / −0.0008 / −0.0025 | 质量折算上限/下限、三项权重(D/SNR/LEN)、平滑系数、衰减系数 O2/O1(原 `TRACK_QUALITY_*`) |
| `[track_index] track_index_max` | 500 | 航迹号上限(原 `MAX_TRACK_INDEX`,内部航迹号表按该值分配) |
| `[beam_control] default_track_beam_type` / `tas_track_num_max` | 1 / 4(**机扫配 1**) | 跟踪波束类型(原硬编码 1)/ 最大同时跟踪 TAS 目标数(原 `MAX_TAS_NUM`) |
| `[singer_model] model_1/2/3_Q_fast/slow` | 0.000001 / 0.01 / 1.0 | 三个 Singer 模型的过程噪声强度(分档见 5.2;原 `RadarPara.Model*_Q_*`) |
| `[track_init_prohibite_area]` / `[tas_prohibite_area]` | `*_num = 0` + 空数组 | 起批屏蔽区 / TAS 禁止区(0~30 组,4 个数组长度必须等于 `*_num`;原 `RadarPara.track_prohibite_area_*` / `TAS_prohibite_area_*`) |
| 参数 | 影响环节 |
|---|---|
| `work_mode`(0 近程/1 中程/2 远程) | 凝聚是否带速度维约束;TWS 外推步长;关联距离门限分支 |
| `track_start_point_num`(校验 3~9) | 起批所需点数 |
| `V_MAX / V_MIN` | 航迹头-点关联的速度区间 |
| `DATA_RATE_SHORT / MIDDLE / FAR` | TWS 未关联航迹外推步长(秒) |
| `DATA_RATE_TAS`(秒,须 > 0) | TAS 波束输出间隔门控 |
| `Height` | 输出 z = 航迹高度 + 平台高度 |
| `Sys_delay` | 输出位置做 x + vx·Sys_delay 的系统延迟补偿 |
| `Model1/2/3_Q_slow/fast` | 三个 Singer 模型的过程噪声强度(分档见 5.2) |
| `track_prohibite_area_*`(≤30 组) | 起批屏蔽区(距离 m + 方位 °) |
| `TAS_prohibite_area_*`(≤30 组) | TAS 禁止区 |
> 旧 `RadarPara` 中实现未使用的字段(`R_TAS_max/min`、`V_TAS_max/min`、`TAS_Height_1~5`、`cfar_th`、`north_angle`、`Beam_num`)未进入 `RDP_parameter`,TAS 自动开启/退出逻辑仍保持停用。
> `track_start_threshold / track_asso_threshold / track_asso_threshold_tas` 三个字段**当前未被实现读取**,实际门限来自编译期宏。
### 9.2 编译期常量(`parameters.h` 保留)
| 常量 / 宏 | 值 | 说明 |
|---|---|---|
| `MAX_TRACK_NUM` | 500 | 输出航迹数组固定容量(宿主接口维度) |
| `RDP_TAS_QUEUE_CAPACITY` | 30 | TAS 队列容量上限;运行期上限用 `tas_track_num_max` |
| `PI` / `Round(x)` | 3.1415926f / 宏 | 圆周率 / 四舍五入取整 |
| 版本宏 | 1.5.9 + `GENERIC` | `VERSION_STRING` → `V1.5.9.GENERIC`(机扫/相扫合并后无体制后缀) |
| `RDP_LOG_TRACE` / `DEBUG` / `INFO` / `WARN` / `ERROR` / `CRITICAL` | 宏(映射 spdlog) | 日志级别宏(fmt 风格格式化),`RDP_LOG_ERR` 为 `RDP_LOG_ERROR` 别名;低于 `SPDLOG_ACTIVE_LEVEL` 的调用在编译期被剔除(实参不求值) |
| `RDP_LOG_DIR` / `RDP_RECORD_*` | `.\rdp_logs\` / — | 日志文件目录(`rdp_YYYYMMDD_HHMMSS.log`,每次 `GetB()` 一个文件,同秒冲突追加 `_1`~`_999`);数据记录开关与性能参数。日志**无** `RDP_LOG_ENABLE` 开关,级别由 CMake 选项 `RDP_LOG_LEVEL` → `SPDLOG_ACTIVE_LEVEL` 在编译期控制(见 README 4.3、10.4) |
> 构建期扫描体制宏已随「机扫/相扫版本合并」移除:`CMakeLists.txt` 不再有 `RADAR_SCAN_MODE`,也不再定义 `PHASE_SCANNING` / `MECHANICAL_SCANNING`;`parameters.h` 的 `VERSION_INFO` 固定为 `GENERIC`(`V1.5.9.GENERIC`)。判圈由配置项 `scan_mode` 决定(见 2.2);`UNCONF_TARGET` 宏已删除(起批处 `Target_Type` 直接写 0)。
---
@@ -590,17 +610,24 @@ flowchart TD
**容量与边界**
- 点迹数上限 150(前处理已限幅);航迹数/航迹号上限 500,全满时新目标不再起批;
- 屏蔽区/禁止区的组数按 30 组数组定义,配置数量未做上限校验;
- 屏蔽区/禁止区的组数按 30 组数组定义;配置入口已由加载器强制 `*_num ∈ [0,30]` 且 4 个数组长度等于 `*_num`(非法配置在 `parameters_reload` 阶段即失败),内部实现不再二次校验;
- 高度平滑缓存只增不减,长时运行下每条航迹的内存占用会持续增长。
**外推、消亡与输出门控(50e0b5c / fc49707 / 42b598e / e8c56f9)**
**外推、消亡与输出门控(50e0b5c / fc49707 / 42b598e / e8c56f9 / V1.5.8)**
- **外推圈数上限**:TWS 只在 `Extrapolate_round <= MAX_EXTRAPOLATE_NUM`(3)时预测并输出;TAS 相反,`Extrapolate_round >= MAX_EXTRAPOLATE_NUM_TAS`(相扫 5 / 机扫 48)时才预测并输出。两处比较方向不一致,此处仅如实记录,**不判定为缺陷**;
- **消亡判据**由 `>=` 改为 `>`,并区分重点目标:普通 TWS 航迹 `> TRACK_DIE_ROUND`(6)、重点航迹(`primary_target_flag==1`)`> TRACK_DIE_ROUND_PRIMARY`(10)、TAS 航迹 `> TRACK_DIE_ROUND_TAS`(相扫 10 / 机扫 96);`manual_delete_flag == 1` 单独判为消亡;因外推上限(3)小于消亡轮数(6),普通 TWS 航迹停止预测后还会连续 3 圈不向宿主输出;
- **外推圈数上限**:TWS 只在 `Extrapolate_round <= extrapolate_max`(配置项 3)时预测并输出;TAS 相反,`Extrapolate_round >= extrapolate_max_tas`(配置项,样例相扫 5 / 机扫需配 48)时才预测并输出。两处比较方向不一致,此处仅如实记录,**不判定为缺陷**;
- **普通 TWS 航迹消亡改按质量(V1.5.8)**:`Quality < track_die_quality`(配置项 0.3)即消亡,`track_die_round`(配置项 6)不再被引用;重点航迹仍按 `> track_die_round_pramary`(配置项 10)、TAS 仍按 `> track_die_round_tas`(配置项;样例相扫 20 / 机扫需配 96);`manual_delete_flag == 1` 单独判为消亡;
- **航迹质量(V1.5.8)**:内部 0~1,临时航迹每延长一点按「距离+SNR」更新、关联实点按「距离+SNR+有效点占比」平滑更新、未关联期间按 `Unupdated_time` 二次衰减;起批需达按长度分级的最低质量;输出 `Track.Quality` 为 0~100(×100)。TAS 通道不维护质量;
- **输出门控**:TWS 与 TAS 的输出航迹循环都新增 `Track_Update_Flag == 1` 判定并在输出后清零,只输出**本圈关联到实点或成功外推**的航迹(此前 TWS 每圈把航迹表中全部可靠航迹各输出一行);
- **接近/远离判据**(fc49707 + 42b598e):`Flag_Direction` 不再用实测径向速度 `vr_point > 0`(受多普勒速度模糊影响符号可能取反),改为用 IMM 融合速度方向角判定——`Direction_Angle = atan2(X[4], X[1])`(弧度转度后输出到 `Track.Direction_Angle`),`x`、`y` 为经 `Work_Parameter.Sys_delay` 补偿后的位置、`r` 为其模,`acos((-x·cosθ - y·sinθ)/r) < π/2` 判为靠近(1)、否则远离(0),等价于"速度方向与雷达→目标位置方向夹角 > 90°"。中间版本 fc49707 曾写为 `acos((-x·vx - y·vy)/r/Range_V) < π/4`(按合速度归一化、门限 45°),42b598e 改为上式;
- **接近/远离判据**(fc49707 + 42b598e):`Flag_Direction` 不再用实测径向速度 `vr_point > 0`(受多普勒速度模糊影响符号可能取反),改为用 IMM 融合速度方向角判定——`Direction_Angle = atan2(X[4], X[1])`(弧度转度后输出到 `Track.Direction_Angle`),`x`、`y` 为经配置项 `radar_system_delay` 补偿后的位置、`r` 为其模,`acos((-x·cosθ - y·sinθ)/r) < π/2` 判为靠近(1)、否则远离(0),等价于"速度方向与雷达→目标位置方向夹角 > 90°"。中间版本 fc49707 曾写为 `acos((-x·vx - y·vy)/r/Range_V) < π/4`(按合速度归一化、门限 45°),42b598e 改为上式;
- **归一化幅度 / 速度维 FFT 点数(e8c56f9 新增,输入侧已打通)**:`DataRev` 新增 `amp_asv`(归一化幅度 dB)、`prn_fftn`(速度维 FFT 点数),`Track`/内部 `PointRecv`/`Trust_Track` 也新增同名成对字段;`data_preprocess()` 现已在 TWS 与 TAS 两条分支都把这两个字段搬入 `PointRecv`(并随 `RdpRecPoint` 落库),因此 `track_asso.cpp` 写入航迹并在 TWS 输出行写回的 `amp_asv_point` / `prn_fftn_point` 有有效数据来源;**TAS 的关联与输出路径(`track_asso_tas.cpp`)仍未涉及这两个字段**,TAS 输出行中它们保持宿主原值。
- **版本号与记录标识(b227ae1 / c3c7e66)**:`parameters.h` 新增版本宏并拼装为 `VERSION_STRING`(当前 `V1.5.6.PS` / `V1.5.6.MS`),实例创建后可由基类公开成员 `version_info` 读取;`Data_Process` 新增 `point_id` / `frame_id` / `circle_id` 自增计数(`circle_id` 在 TWS 满一圈、TAS 每帧末自增),`PointRecv`/`Temp_track`/`Trust_Track` 新增对应的标识字段,记录功能据此把「输入点迹 → 凝聚点迹 → 临时航迹 → 可靠航迹 → 关联信息」串成同一条链路。
- **版本号与记录标识(b227ae1 / c3c7e66 / 5cb67cc)**:`parameters.h` 新增版本宏并拼装为 `VERSION_STRING`(机扫/相扫合并后为 `V1.5.9.GENERIC`;历史上 b227ae1 时为 `V1.5.8.PS` / `V1.5.8.MS`),实例创建后可由基类公开成员 `version_info` 读取;`Data_Process` 新增 `point_id` / `frame_id` / `circle_id` 自增计数(`circle_id` 在 TWS 每判满一圈、TAS 每帧末自增),`PointRecv`/`Temp_track`/`Trust_Track` 新增对应的标识字段,记录功能据此把「输入点迹 → 凝聚点迹 → 临时航迹 → 可靠航迹 → 关联信息」串成同一条链路;可靠航迹与关联信息的记录点为 TWS/TAS 双通道(关联信息以 `Asso_Info::tas_flag` 区分来源)。
**航迹指纹(V1.5.9,0f3da99)**
- 起批时对**末点**计算 `Track_fingerprint`(16 bit):`r`/`azi`/`vr`/`RCS`/`CPI_time` 分别量化到 0.01 m / 0.001° / 0.01 m·s⁻¹ / 0.1 dBsm / 1 ms,经 splitmix64 逐个混合后折叠为 16 bit(`track_init.cpp::computeFingerprint()`);
- TAS 输入帧的 `DataRev::TAS_track_index` 与 TAS 波束输出的 `TrackingBeam::TAS_track_index` 均改为**指纹**(字段名未变);`track_process(model=3)` 按指纹定位航迹,队列与内部关联仍用 `Track_Index`;
- 16 bit 指纹存在碰撞可能,且同一目标消亡后重新起批通常得到不同指纹;记录实体 `RdpRecTrustTrack` 尚未包含该字段(详见 README 9.12、11.2)。
**未启用**
@@ -616,13 +643,13 @@ flowchart TD
| 项 | README 描述(更新前) | 当前源码 |
|---|---|---|
| 满圈判据 | 当前方位波位号 < 上一帧波位号 | **方位波位号 == 0**(7d5a56f,适应机扫转台反转) |
| 满圈判据 | 当前方位波位号 < 上一帧波位号 | 判据由配置项 `scan_mode` 运行时选择(原按体制编译期分支):**scan_mode=0 相扫**当前波位号 ≤ 上一帧波位号(3e6763c,等价于原判据)、**scan_mode=1 机扫**方位波位号 == 0(7d5a56f,适应机扫转台反转) |
| 点-航迹头判据 | 按距离分段收紧速度门限(半速、1/2~1/3 速) | 该分支注释停用;改为统一速度区间 **+ 两条多普勒-几何一致性约束**(7d5a56f) |
| 起始输出历史点 | "临时航迹的全部 L 个点" | 实际输出**前 `track_start_point_num` 个点** |
| 起始输出历史点 | "临时航迹的全部 L 个点" | 实际输出**前 `track_init_point_num_min` 个点** |
| 起始输入过滤 | "track_init 跳过 Use_Flag==1 的点" | 上游 `track_asso_process()` 结束前已把 `Use_Flag==1` 的点迹从 `point_recv` 删除;`track_init` 入口(L59-70)也会再剔除一次 `Use_Flag==1` 的点,两道处理叠加,效果一致 |
| 日志开关 | "RDP_LOG_ENABLE 默认开启" | `parameters.h` 中该宏当前为**注释状态**(不落盘,日志进控制台) |
| 日志开关 | "RDP_LOG_ENABLE 默认开启" | `RDP_LOG_ENABLE` 开关已删除(日志组件改为 spdlog):日志**始终**同时写标准输出与 `.\rdp_logs\` 下按日期+时间命名的日志文件(每次 `GetB()` 一个文件),级别由 CMake 选项 `RDP_LOG_LEVEL` 映射 `SPDLOG_ACTIVE_LEVEL` 在编译期控制(`OFF` 时编译期关闭且不创建日志文件,见 README 4.3、10.4) |
| 起始步骤顺序 | "航迹头(1 点)→ 点-航迹头关联(2 点)→ 点-临时航迹关联(≥3 点)→ 起批" | 每圈实际顺序为**点 × ≥2 点临时航迹关联 → 点 × 1 点航迹头关联 → 剩余点转新航迹头 → 长度达标的临时航迹起批**,消亡最后执行(`track_init.cpp` L49-126,见第 6 节) |
> 后续:README 与 `track_init_readme.md`(航迹起始专项说明)已按上表结论同步(见 CHANGELOG 的 Unreleased 记录);流程落地实现仍以本文为准。
> 后续:README 与 `track_init_readme.md`(航迹起始专项说明)已按上表结论同步(见 CHANGELOG 对应版本的记录);流程落地实现仍以本文为准。
>
> 补充修订(本次):起始流程的**步骤顺序**原先在本文档(第 0、6 节)与 README(第 2、5.2、8.3、9.3 节)中都写作「航迹头 → 2 点 → ≥3 点 → 起批」,与源码每圈「点 × 临时航迹关联 → 点 × 航迹头关联 → 剩余点转航迹头 → 达标者起批」的顺序相反,现已按源码修正,并补充说明两个关联阶段互不排斥、均不做一对一分配。
+214 -74
View File
@@ -2,17 +2,34 @@
> 面向调试的数据记录框架:把内部结构体按「一个记录实体一张表」写入 SQLite,供事后查询分析。
> 本功能**默认只在 Debug 构建中启用**,Release/交付构建完全不编译(无依赖、无代码、无性能损失)。
> V1.5.8 优化后,记录链路为「处理线程收集源结构体 → 有界队列 → 独立写线程构造实体并批量事务入库」,
> 雷达处理线程只承担拷贝与入队开销;写线程跟不上时丢弃数据而不是阻塞处理。
> 相关代码:`data_process_class_dll/rdp_record.h`、`rdp_record_entity.h`、`rdp_record.cpp`;
> 第三方依赖来源见 `third_party/ormpp/UPSTREAM.md`。
> 相关参数:`parameters.h` 的「数据记录性能参数」段;第三方依赖来源见 `third_party/ormpp/UPSTREAM.md`。
> 记录模块的日志经 `rdp_log`(内置 spdlog,`third_party/spdlog`)输出,与 DLL 日志同样双写标准输出与 `.\rdp_logs\` 日志文件(分级 info/warn/error,见 README 2、4.3)。
---
## 1. 目的与范围
- **目的**:调试期把关键中间数据落库(点迹、航迹、关联结果……),用于事后 SQL 查询与对比分析;
- **框架性设计**:本功能**不预设记录哪些数据**。新增一类记录数据的成本固定为三步:
加记录实体结构体 → 注册 → 在需要的位置写一行记录点宏(配方见第 5 节);
- **非目标**:不做异步写入、不做查询/回放工具、不做数据裁剪与轮转、不追求多线程写入性能。
- **框架性设计**:本功能**不预设记录哪些数据**。新增一类记录数据的成本固定为四步:
加记录实体 → 加异步入队接口与宏 → 在写线程注册批次/构造/提交分支 → 业务侧收集后调用宏(配方见第 6 节);
- **性能模型(V1.5.8 优化)**:
- 记录点只做「帧/圈内收集源结构体 → 一次入队」,处理线程上只有结构体拷贝 + 队列加锁 push 的开销;
- 实体构造(`X`/`P` 等数组转文本)与 SQLite 写入由独立写线程完成,按表批量事务提交;
- 队列**有界**(默认 64 MB / 4096 批),写线程跟不上时**整批丢弃并计数**,绝不阻塞雷达处理线程;
- 数据库使用 WAL + `synchronous=OFF` + 8 MB 页缓存,建表后去掉 `AUTOINCREMENT`,以换取调试场景下的写入吞吐;
- **非目标(本轮明确不做)**:
- 不做关联信息裁剪(分析报告中记为 **P0-a**):⑥ 关联信息仍按「每个候选一行」落库,单圈行数为
「点迹数 × 航迹数」的 M×N 量级;
- 不做记录策略/轮转/在线回放(记为 **P2**):不按容量/时长切库、不清理旧库、不提供查询或回放工具;
- 不保证**跨表**写入顺序(同一张表内的行顺序保持);
- 不保证进程崩溃时的数据完整性:队列尾部与 WAL 尾部可能丢失(异步 + `synchronous=OFF` 的代价);
- 不改变记录行数与表列(与优化前完全一致,见第 10 节验证)。
- **性能摘要**(单机 dup 场景实测,详见第 10 节):普通帧每帧新增耗时 p99 ≈ 0.09~0.11 ms;
判圈帧 track 新增中位 ≈ 2.9~4.1 ms、p99 ≈ 13~24 ms(多次验收运行;200 目标/150 点压测场景,因未采用 P0-a,单圈约 12.3 万关联候选,
属该实现的压测上限;受控 50 目标/50 点场景最大 5.96 ms)。
## 2. 开关与构建
@@ -20,47 +37,100 @@
|---|---|
| 开关 | CMake 缓存变量 `RDP_DATA_RECORD`,取值 `AUTO`(默认)/ `ON` / `OFF` |
| AUTO 语义 | `Debug` 构建启用、`Release`(及其它)构建关闭 |
| 生效方式 | 启用时构建系统定义 `RDP_RECORD_ENABLE` 并额外编译静态库 `rdp_record`(`rdp_record.cpp` + 内置 `sqlite3.c`)并链接进 DLL |
| 关闭时的保证 | 记录模块源码**不参与编译**;`RDP_RECORD(...)` / `RDP_RECORD_BATCH(...)` 展开为 `((void)0)`(实参不求值,也不会产生「变量已初始化但未使用」告警);DLL 不新增依赖与导出符号 |
| 生效方式 | 启用时构建系统定义 `RDP_RECORD_ENABLE` 并额外编译静态库 `rdp_record`(`rdp_record.cpp` + 内置 `sqlite3.c`)并链接进 DLL(`CMakeLists.txt` 的 `if(RDP_RECORD_ENABLED)` 段) |
| 关闭时的保证 | 记录模块源码**不参与编译**(`CMakeLists.txt` 的 `if(RDP_RECORD_ENABLED)` 内才 `add_library`/链接);所有记录宏展开为 `((void)0)`(实参不求值,不产生「变量已初始化但未使用」告警);DLL 不新增依赖与导出符号 |
```bat
:: 交付/Release:关闭(AUTO 的默认结果,也可显式 OFF)
.vscode\scripts\configure.bat build/X256_PS PHASE_SCANNING Release OFF
.vscode\scripts\build.bat build/X256_PS
.vscode\scripts\configure.bat Release OFF
.vscode\scripts\build.bat Release OFF
:: Debug:启用(AUTO 的默认结果,也可显式 ON)
.vscode\scripts\configure.bat build/X256_PS PHASE_SCANNING Debug ON
.vscode\scripts\build.bat build/X256_PS
.vscode\scripts\configure.bat Debug ON
.vscode\scripts\build.bat Debug ON
:: 一次性「配置 + 构建」单个配置(VS Code 任务「配置并构建(...)」的内部实现)
.vscode\scripts\build_config.bat Debug ON
:: 直接用 cmake(需先进入 MSVC 2022 x64 环境)
cmake -S . -B build/debug_rec -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Debug ^
-DRADAR_SCAN_MODE=PHASE_SCANNING -DRDP_DATA_RECORD=ON
cmake --build build/debug_rec
cmake -S . -B build/Debug/REC_ON -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Debug ^
-DRDP_DATA_RECORD=ON
cmake --build build/Debug/REC_ON
```
构建目录按「构建类型 + 记录模式」隔离:`build/<Debug|Release>/<REC_AUTO|REC_OFF|REC_ON>`(机扫/相扫已合并为同一版本,无扫描体制维度),因此 REC_OFF 与 REC_ON 的产物可以并存;VS Code 任务「配置并构建(...)」与脚本 `build_config.bat`/`clean_all.bat` 均基于该布局(见 README 4.3)。
配置阶段会打印开关状态:`数据记录调试功能:启用(C++20 静态库 rdp_record + 内置 SQLite)` 或 `...:关闭(不编译记录模块,RDP_RECORD 为空操作)`。
**语言标准与依赖**:记录模块以 **C++20** 独立编译(ormpp 要求 `CMAKE_CXX_STANDARD 20`),并编译内置的 SQLite amalgamation(因此启用时需要 C 编译器,随 VS 2022 工具集提供);**雷达处理代码与内置 Eigen 仍为 C++17**,二者通过 `rdp_record.h` 的「仅声明 + 显式实例化」跨标准边界:调用方(C++17)只把记录实体按 `const` 引用递给 `rdp_record::Write<T>()`,实现在 `rdp_record.cpp`(C++20)中显式实例化。
**「关闭时零代码」的依据**:
1. 关闭时构建系统不创建 `rdp_record` 静态库,也不把 `sqlite3.c` 加入任何目标;
2. `rdp_record.h` 在未定义 `RDP_RECORD_ENABLE` 时把 `RDP_RECORD_POINTS`/`RDP_RECORD_COHESION_TWS`/`RDP_RECORD_COHESION_TAS`/`RDP_RECORD_TEMP_TRACKS`/`RDP_RECORD_TRUST_TRACKS`/`RDP_RECORD_ASSO_INFOS` 以及遗留的 `RDP_RECORD`/`RDP_RECORD_BATCH` 全部展开为 `((void)0)`,并提供 `Init/Shutdown/db_path/stats/Write` 的内联空实现;
3. 6 个业务文件(`data_process.cpp`、`dot_coh.cpp`、`dot_coh_tas.cpp`、`track_init.cpp`、`track_asso.cpp`、`track_asso_tas.cpp`)中的收集变量、收集语句与宏调用**整体包在 `#ifdef RDP_RECORD_ENABLE` 内**;这些文件已不再包含 `rdp_record_entity.h`,因此实体结构体、`arrayToString()` 与 `struct.h` 不会经记录模块被处理代码拖入;
4. 实测 4 种配置(Release/Debug × REC_ON/OFF)均 0 warning、0 error(机扫/相扫合并后不再有 PS/MS 维度),且 OFF 构建的 DLL 不新增依赖/导出符号。
**语言标准与依赖**:记录模块以 **C++20** 独立编译(ormpp 要求 `CMAKE_CXX_STANDARD 20`),并编译内置的 SQLite amalgamation(因此启用时需要 C 编译器,随 VS 2022 工具集提供);**雷达处理代码与内置 Eigen 仍为 C++17**,二者通过 `rdp_record.h` 的「`Write<T>` 仅声明 + 显式实例化」跨标准边界:调用方(C++17)不实例化记录实现,实现在 `rdp_record.cpp`(C++20)中显式实例化。V1.5.8 起常规记录点改走**非模板**的 `Write*` 异步入队接口,跨标准边界更简单;模板 `Write<T>` 仅作为遗留同步接口保留。
## 3. 运行期行为
| 项目 | 说明 |
|---|---|
| 存放目录 | `RDP_RECORD_DB_DIR`(`parameters.h`,默认 `.\rdp_records\`,相对宿主进程当前工作目录,不存在时自动创建) |
| 文件名 | `rdp_record_YYYYMMDD_HHMMSS.db`(**每次运行一个新文件**,与 `rdp_logs` 的日志命名风格一致) |
| 初始化 | `Data_Process_Factory::GetB()` 内调用 `rdp_record::Init()`(幂等):建目录 → 开库 → 打印记录库路径 |
| 关闭 | `Data_Process_Factory::Destroy()` 内调用 `rdp_record::Shutdown()`;宿主未调用时由静态析构兜底关库 |
| 建表 | 首次写入某记录实体时自动执行 `CREATE TABLE IF NOT EXISTS`(每实体只尝试一次) |
| PRAGMA | `synchronous=OFF`(调试取舍:提交时不再 fsync,崩溃可能丢失尾部数据;如需崩溃安全可改为 `NORMAL` 或自行在 sqlite3 层开启 WAL)。未设置 `journal_mode`——ormpp 的 `execute()` 只接受不返回结果行的语句 |
| 线程模型 | 单连接 + 内部互斥锁串行化写入;宿主多线程调用不会破坏数据,但**不保证高并发写入性能**(调试功能,按要求不做异步) |
| 失败降级 | 目录不可建/库打不开:`Init()` 返回 `false`,只打印一次日志,之后所有写入静默忽略;建表或插入失败:**每个实体只提示一次**,不重试、不抛出、不影响雷达数据处理 |
| 自增主键 | 每个记录实体的首字段固定 `int64_t id`:建表时登记自增主键,插入时自动跳过该列(写入时固定填 `0`,由 SQLite 分配) |
| 文件名 | `rdp_record_YYYYMMDD_HHMMSS.db`(**每次运行一个新文件**,与 `rdp_logs` 下日志文件(`rdp_YYYYMMDD_HHMMSS.log`)的命名风格一致);同一秒内重复 `Init`(如 Destroy→GetB 重连)或同目录多进程启动时,检测到文件已存在则自动追加 `_1`/`_2`…(最多尝试 999 个后缀),不复用同一库文件 |
| 初始化 | `Data_Process_Factory::GetB()` 内调用 `rdp_record::Init()`:建目录 → 生成文件名 → 打开 SQLite → 应用 PRAGMA → **启动记录写线程** → 置 ready;已初始化时幂等,初始化失败返回 `false` 且不抛异常 |
| 写线程 | 独立 `std::thread`,Windows 下用 `SetThreadPriority(..., THREAD_PRIORITY_BELOW_NORMAL)` 把 CPU 优先级降到后台(I/O 保持正常优先级,避免排空过慢);写线程是 SQLite 连接的主要使用者,处理线程不直接访问数据库(遗留同步接口经 `db_mtx` 与写线程串行化) |
| 队列与丢弃 | 处理线程把「一种表的一批源结构体」放进有界 `std::deque`;上限为 `RDP_RECORD_QUEUE_CAP_BYTES`(默认 64 MB,按容器容量近似统计)且 `RDP_RECORD_QUEUE_MAX_BATCHES`(默认 4096 批)。任一上限将超出时**整批丢弃**并累计 `dropped_rows`,不阻塞处理线程;丢弃日志按 `RDP_RECORD_DROP_LOG_INTERVAL`(默认每 1024 行)限频提示 |
| 批事务 | 写线程按表把队列数据追加到 pending 缓冲,当 pending 行数达到 `RDP_RECORD_WRITE_BATCH_ROWS`(默认 4096 行)或为凑批等待 `RDP_RECORD_WRITE_MAX_WAIT_MS`(默认 50 ms)超时后,按**表**分别提交;同一张表的一批数据在同一个 `BEGIN/COMMIT` 事务内插入 |
| 实体构造 | 「源结构体 → 记录实体」(`PointRecv`→`RdpRecPoint`、`Trust_Track`→`RdpRecTrustTrack` 等,含数组转文本)**全部在写线程完成**;处理线程只拷贝源结构体 |
| PRAGMA | `journal_mode=WAL`(`RDP_RECORD_SQLITE_WAL=1` 时尝试,失败回退默认 journal 并提示);`synchronous=OFF`;`cache_size=-8192`(8 MB);`busy_timeout=2000` ms。注:`PRAGMA journal_mode`/`busy_timeout` 会返回结果行,代码用 `query_s<std::tuple<...>>` 消费 |
| 建表 | 首次写入某记录实体时自动建表(每表只尝试一次);ormpp 的 DDL 硬编码 `AUTOINCREMENT`,建表后读 `sqlite_master` 中的 DDL 去掉该关键字并 `DROP` 重建为空表(普通 `INTEGER PRIMARY KEY`,省掉每行对 `sqlite_sequence` 的读写);重建失败时回退为保留 `AUTOINCREMENT` 的表并告警 |
| 失败降级 | 目录不可建/库打不开/取时间失败:`Init()` 返回 `false`,仅打印日志,之后所有写入静默忽略;建表或插入失败:**每张表只提示一次**并永久跳过该表,不重试、不抛出;异步批量写入连续失败达到 `RDP_RECORD_MAX_CONSECUTIVE_ERRORS`(默认 10)次则**全局熔断**(停止记录、丢弃队列并退出写线程) |
| 关闭 | `Data_Process_Factory::Destroy()` 内调用 `rdp_record::Shutdown()`:① 停止接收新数据 → ② 唤醒写线程**排空队列与 pending** → ③ 打印统计 → ④ `PRAGMA wal_checkpoint(TRUNCATE)` → ⑤ 关库;宿主未调用 `Destroy()` 时由静态对象析构兜底停止线程/关库 |
| 重连 | `Destroy()` 之后再 `GetB()`:`Shutdown()` 已释放连接与状态,`Init()` 会**重新建库**(新文件;同秒冲突追加后缀),记录继续写入新库 |
| 写入顺序 | 队列 FIFO;同一张表内的行顺序与采集顺序一致;**不同表之间的提交先后不保证**(写线程按固定表顺序 flush) |
| 运行统计 | `rdp_record::stats()` 任意时刻可读(原子计数、线程安全),`Shutdown()` 时自动打印一行 `[rdp_record] stats(shutdown): ...` |
| 静默忽略 | `Init()` 之前、初始化失败之后、`Shutdown()` 之后的写入一律静默忽略(未初始化时不计入统计;已初始化但已停止/失败时计入 `dropped_rows`) |
## 4. 记录实体(表)约定
> 遗留接口:`RDP_RECORD(...)`/`RDP_RECORD_BATCH(...)` 仍映射到模板 `Write<T>`,在**调用线程内直接写库**(经 `db_mtx` 与写线程串行化)会阻塞,仅供测试/特殊场景;新记录点请使用第 4 节的异步入队接口与宏。
## 4. 接口与宏一览
`rdp_record.h` 对外接口:
| 接口 | 说明 |
|---|---|
| `bool Init()` | 建目录、开库、应用 PRAGMA、启动写线程;由 `GetB()` 调用 |
| `void Shutdown()` | 停止入队、排空队列、checkpoint、关库、打印统计;由 `Destroy()` 调用 |
| `std::string db_path()` | 当前记录库全路径(未启用/未成功打开时为空串) |
| `Stats stats()` | 当前统计快照,线程安全 |
| `void WritePoints(std::vector<PointRecv> v)` | 输入点迹 → `RdpRecPoint`(TWS/TAS 共用) |
| `void WriteCohesionTWS(std::vector<PointRecv> v)` | TWS 凝聚后点迹 → `RdpRecPointCohesionTWS` |
| `void WriteCohesionTAS(std::vector<PointRecv> v)` | TAS 凝聚后点迹 → `RdpRecPointCohesionTAS` |
| `void WriteTempTracks(std::vector<Temp_track> v)` | 临时航迹 → `RdpRecTempTrack` |
| `void WriteTrustTracks(std::vector<Trust_Track> v)` | 可靠航迹 → `RdpRecTrustTrack` |
| `void WriteAssoInfos(std::vector<Asso_Info> v)` | 点航关联信息 → `RdpRecAssoInfo` |
| `template <typename T> void Write(const T&)` / `(const std::vector<T>&)` | **遗留**同步写记录实体(会阻塞调用线程) |
| `struct Stats` | `enqueued_rows` 已入队行数、`dropped_rows` 丢弃行数、`written_rows` 已写库行数、`transactions` 提交事务数、`write_errors` 写失败次数、`queue_peak_bytes`/`queue_peak_rows` 队列峰值 |
记录点宏(参数均为**源结构体 vector 的左值**,宏内部 `std::move`,调用后该 vector 为空):
| 宏 | 记录的源结构体 | 目标表 |
|---|---|---|
| `RDP_RECORD_POINTS(v)` | `std::vector<PointRecv>` | `RdpRecPoint` |
| `RDP_RECORD_COHESION_TWS(v)` | `std::vector<PointRecv>` | `RdpRecPointCohesionTWS` |
| `RDP_RECORD_COHESION_TAS(v)` | `std::vector<PointRecv>` | `RdpRecPointCohesionTAS` |
| `RDP_RECORD_TEMP_TRACKS(v)` | `std::vector<Temp_track>` | `RdpRecTempTrack` |
| `RDP_RECORD_TRUST_TRACKS(v)` | `std::vector<Trust_Track>` | `RdpRecTrustTrack` |
| `RDP_RECORD_ASSO_INFOS(v)` | `std::vector<Asso_Info>` | `RdpRecAssoInfo` |
| `RDP_RECORD(...)` / `RDP_RECORD_BATCH(...)` | 记录实体(单条/批量,可变参数) | 对应实体表(**遗留同步接口**) |
记录功能关闭时上述宏全部展开为 `((void)0)`,实参不求值。
## 5. 记录实体(表)约定
1. 结构体定义在**全局命名空间**(勿放进 `namespace`),推荐以 `RdpRec` 开头;
2. **结构体名即表名**(ormpp 取「短结构体名」),无需任何建表代码;
3. 首字段固定为 `int64_t id`;
3. 首字段固定为 `int64_t id`:SQLite 自增主键(写入时固定填 `0`,由 SQLite 分配);自 V1.5.8 起 **DDL 不再使用 `AUTOINCREMENT`**——建表后按 `sqlite_master` 的 DDL 去掉该关键字重建,语义仍是 `ROWID` 自增、`id` 单调递增,但不再维护 `sqlite_sequence`;
4. 字段类型仅限标量,映射关系:
| C++ 类型 | SQLite 类型 | 备注 |
@@ -73,86 +143,118 @@ cmake --build build/debug_rec
| `std::string` | `TEXT` | 用于把数组类状态(如 `X[6]`、`P[6][6]`、`u[3]`)经 `rdp_record_entity.h` 的 `arrayToString()` 先转成文本再入库——这是「禁止数组字段」的替代写法,见第 5 条 |
5. **禁止数组字段**(如 `float speed_dim[129]`、`char[N]`):ormpp 对 C 数组按 C 字符串语义(`strlen`)绑定,会读越界或写入错误数据。需要记录大批量数组时,按下列三种方式之一处理:① 用一个 `std::string` 字段存 `arrayToString(数组)` 的结果(`RdpRecTempTrack`/`RdpRecTrustTrack` 的 `X`/`P`/`X1`~`X3`/`P2`/`P3`/`u` 即此法,多维数组以 `;` 分隔内层、`,` 分隔元素);② 另建「元素展开表」(一行一个元素);③ 改用 blob 字段;
6. 命名约定:字段名带单位后缀(`_ms` 毫秒、`_m` 米、`_deg` 度、`_mps` 米/秒、`_s` 秒),避免单位歧义;
7. **表结构变更不迁移旧表**:增删字段后不会修改已存在的表结构;由于每次运行生成新的 db 文件,正常调试流程天然规避该问题(若手工复用旧库,需自行建新表或删库)。
6. **实体需提供「源结构体 → 实体」的构造函数**(如 `RdpRecTrustTrack(Trust_Track& p)`):实体构造发生在写线程,源结构体 vector 已 move 进队列后才逐条转换;
7. 命名约定:字段名带单位后缀(`_ms` 毫秒、`_m` 米、`_deg` 度、`_mps` 米/秒、`_s` 秒),避免单位歧义;
8. **表结构变更不迁移旧表**:增删字段后不会修改已存在的表结构;由于每次运行生成新的 db 文件,正常调试流程天然规避该问题(若手工复用旧库,需自行建新表或删库)。
## 5. 新增记录点:四步配方
## 6. 新增记录点:四步配方
以「记录航迹号 + 高度」为例:
以「记录航迹号 + 高度」为例(现有 6 张表的记录点均按此模型实现):
**第 1 步 — 声明实体**(`rdp_record_entity.h`「记录实体定义区」,紧随其后写一行 `RDP_RECORD_DECLARE`):
**第 1 步 — 加记录实体**(`rdp_record_entity.h`「记录实体定义区」加结构体并紧跟 `RDP_RECORD_DECLARE`;再到 `rdp_record.cpp`「记录实体注册区」加 `YLT_REFL` + `RDP_RECORD_DEFINE`,字段顺序必须与结构体声明顺序一致):
```cpp
// rdp_record_entity.h
struct RdpRecTrackHeight
{
int64_t id; //自增主键(写入时固定填 0)
int track_index; //航迹号
double height_m; //高度(m)
int Track_Index; //航迹号
double Height; //高度(m)
RdpRecTrackHeight() { id = 0; }
RdpRecTrackHeight(Trust_Track& p) : id(0), Track_Index(p.Track_Index), Height(p.Height) {}
};
RDP_RECORD_DECLARE(RdpRecTrackHeight);
```
**第 2 步 — 注册实体**(`rdp_record.cpp`「记录实体注册区」,字段顺序必须与结构体声明顺序一致):
```cpp
YLT_REFL(RdpRecTrackHeight, id, track_index, height_m);
// rdp_record.cpp 记录实体注册区
YLT_REFL(RdpRecTrackHeight, id, Track_Index, Height);
RDP_RECORD_DEFINE(RdpRecTrackHeight);
```
**第 3 步 — 埋记录点**(在需要记录的位置):
**第 2 步 — 确定源结构体并加异步入队接口/宏**(`rdp_record.h`):源结构体优先复用 `struct.h` 中已有的 `PointRecv`/`Temp_track`/`Trust_Track`/`Asso_Info`(头文件只做前置声明);接口参数为**按值** `std::vector<源结构体>`,宏内部 `std::move`:
```cpp
//单条(就地构造;宏为可变参数,允许花括号内有逗号)
RDP_RECORD(RdpRecTrackHeight{0, trust_track[i].Track_Index, trust_track[i].Height});
// rdp_record.h:namespace rdp_record 内追加
void WriteTrackHeights(std::vector<Trust_Track> v); //RdpRecTrackHeight
//批量(需要语句/局部变量时,整体用 #ifdef 包裹,保证交付构建零代码)
// 宏区(RDP_RECORD_ENABLE 开/关两套)
#ifdef RDP_RECORD_ENABLE
std::vector<RdpRecTrackHeight> recs;
for (size_t i = 0; i < trust_track.size(); ++i)
{
recs.push_back(RdpRecTrackHeight{0, trust_track[i].Track_Index, trust_track[i].Height});
}
RDP_RECORD_BATCH(recs);
#define RDP_RECORD_TRACK_HEIGHTS(v) ::rdp_record::WriteTrackHeights(std::move(v))
#else
#define RDP_RECORD_TRACK_HEIGHTS(v) ((void)0)
#endif
```
**第 4 步 — 无需建表**:首次写入该实体时自动 `CREATE TABLE IF NOT EXISTS`;不同实体自动落到不同表。
**第 3 步 — 在 `rdp_record.cpp` 写线程侧增加批次/构造/提交分支**(新表需要独立的一条入队通道):
> 检查点:若忘记第 2 步(注册/实例化),链接期会报 **LNK2019**(`extern template` 已阻止隐式实例化),这正是配方的自检点。
1. `BatchKind` 枚举加 `TrackHeight`;
2. `Batch` 结构体加 `std::vector<Trust_Track> track_heights;`;
3. `BatchRows`/`BatchBytes` 与 `PendingRows`/`Pending`/`ClearPending` 同步加上该成员(否则队列行数/字节统计失真);
4. `AppendBatch` 加 `case BatchKind::TrackHeight: AppendMove(p.track_heights, b.track_heights); break;`;
5. `FlushPending` 加 `ok &= FlushKind<RdpRecTrackHeight>(st, p.track_heights);`;
6. 在异步入队接口区实现 `WriteTrackHeights`(构造 `Batch`、设置 kind、`std::move` 后调用 `EnqueueBatch`,空 vector 直接返回)。
## 6. 现有记录表与记录点
> 若只是复用现有 6 种源结构体之一,也不能跳过本步:一种入队通道对应一张表;要新增一张表就必须新增 `BatchKind` + `Write*` 接口 + flush 分支。
当前共 **7 个记录实体定义**,其中 **6 张表在用**(第 ⑦ 项已停用)。下表逐项给出记录点、埋点位置、表名与用途;完整字段清单见本节末的「字段速查」。
**第 4 步 — 业务侧收集后调用宏**(在雷达处理线程、数据仍有效时收集;需要语句/局部变量时整体用 `#ifdef` 包裹,保证关闭记录构建零代码):
| # | 记录点 | 位置 | 表 | 说明 |
|---|---|---|---|---|
| ① | 输入点迹(TWS + TAS) | `data_process.cpp::data_preprocess()`,TWS 分支(`point_type == 0`)与 TAS 分支(`point_type == 1`)的逐点循环内 | `RdpRecPoint` | TWS/TAS **共用一张表**,用 `point_type` 区分;每个输入点迹一行,含 `point_id` / `frame_id` / `circle_id` 三个链路标识 |
| ② | TWS 凝聚后点迹 | `dot_coh.cpp::dot_coh_process_buff()`,输出到 `point_recv` 的循环内 | `RdpRecPointCohesionTWS` | 两帧滑窗凝聚后进入关联的点迹 |
| ③ | TAS 凝聚后点迹 | `dot_coh_tas.cpp::dot_coh_tas_process()`,输出到 `point_recv_tas` 的循环内 | `RdpRecPointCohesionTAS` | 单帧凝聚后的 TAS 点迹 |
| ④ | 临时航迹 | `track_init.cpp::track_init_process_logic()` 末尾(**消亡临时航迹之后**),双层遍历 `temp_track` | `RdpRecTempTrack` | 每条临时航迹的每个点一行,含 `temp_track_id` / `point_id` / `circle_id`;因在消亡之后记录,表中是「本圈存活下来 + 本圈新建」的临时航迹 |
| ⑤ | 可靠航迹 | `track_asso.cpp::track_asso_process()` 的**输出循环内**(`Track_Update_Flag==1` 的分支末尾) | `RdpRecTrustTrack` | 内部 `Trust_Track` 全量落库(含 IMM 的 `X`/`P`/`X1`~`X3`/`P2`/`P3`/`u`,数组经 `arrayToString()` 存为文本列)。**只记录本圈有输出的航迹,且仅 TWS 通道**——`track_asso_tas.cpp`(TAS)与 `track_init.cpp`(起批)均未埋此点 |
| ⑥ | 点航关联信息 | `track_asso.cpp::model_filter()` 三处:未通过门限的候选、被最近邻取中的候选、因航迹/点迹已被消费而删表的候选 | `RdpRecAssoInfo` | 每个候选一行(`d1`/`d2`/`d3`/`d_min`、`delta_T`/`delta_h`/`R`/`VT`、`thd_d`/`thd_h`/`thd_r`),并以 `min_d_flag` 标记是否被最近邻取中,可用于复现关联决策 |
| ⑦ | (已停用)本圈输出航迹 | 原 `data_process.cpp::track_process()` 的 `model == 1/3` 分支 | ~~`RdpRecTrackOut`~~ | 该实体与记录点已在 b227ae1 中整体注释停用,改由 ⑤ 记录内部航迹;`rdp_record.cpp` 与 `rdp_record_entity.h` 中保留注释代码以备参考 |
```cpp
#ifdef RDP_RECORD_ENABLE
std::vector<Trust_Track> rec_heights;
rec_heights.reserve(trust_track->size());
for (size_t i = 0; i < trust_track->size(); ++i)
{
if ((*trust_track)[i].Track_Update_Flag == 1)
{
rec_heights.push_back((*trust_track)[i]);
}
}
RDP_RECORD_TRACK_HEIGHTS(rec_heights); //宏内部 std::move,调用后 rec_heights 为空
#endif
```
> 表清单即上表 ①②③④⑤⑥ 六张在用表(`rdp_record_entity.h` 中共 7 个实体定义,其中第 ⑦ 项已停用)。
**无需建表**:首次写入该实体时自动建表;不同实体自动落到不同表。
**链路串联**:①→②/③(点迹 → 凝聚后点迹)按 `point_id` 关联;②/③→④/⑤ 按 `point_id` 与 `circle_id` 关联;④→⑤ 通过起批时透传的 `track_id`(取自临时航迹末点的 `temp_track_id`)关联;⑤→⑥ 按 `track_id` 与 `point_id` 关联。`circle_id` 在 TWS 满一圈(`Beam_index_aiz == 0`)、TAS 每帧处理结束时自增,可用来划分「第几圈/第几帧」。
> **自检点(忘记注册会链接失败)**:
> - 忘记 `RDP_RECORD_DEFINE`(或 `RDP_RECORD_DECLARE`)而使用了遗留同步宏 `RDP_RECORD(实体{...})`:链接期报 **LNK2019**(`rdp_record::Write<T>` 只有声明/显式实例化,`extern template` 阻止 C++17 调用方隐式实例化);
> - 忘记 `YLT_REFL`:`rdp_record.cpp` 编译期在 ormpp 反射处报错;
> - 新增表若忘了第 3 步的队列分支,编译期会因缺少对应 `Write*` 实现/`FlushKind` 而报错,或运行期该表永远没有数据。
> - 记录点必须在处理线程上、对**当前有效**的源结构体就地收集;不要保存指针/引用,也不要跨线程复用同一 vector。
## 7. 现有记录表与记录点
当前共 **6 个记录实体定义、6 张在用表**(`rdp_record_entity.h` 中 6 个结构体;历史示例 `RdpRecTrackOut` 的实体与记录点早已整体移除,仅在 `rdp_record.cpp` 注册区以注释保留两行备查)。12 个业务记录点全部改为「帧/圈内收集源结构体 → 一次入队」:
| # | 记录点 | 位置(文件:行) | 记录宏 | 表 | 说明 |
|---|---|---|---|---|---|
| ① | 输入点迹(TWS + TAS) | `data_process.cpp:75`(TWS 分支,判圈 return 之前)/ `data_process.cpp:153`(TAS 分支,`circle_id++` 之前) | `RDP_RECORD_POINTS` | `RdpRecPoint` | TWS/TAS **共用一张表**,用 `point_type` 区分;每个输入点迹一行,含 `point_id` / `frame_id` / `circle_id` 三个链路标识 |
| ② | TWS 凝聚后点迹 | `dot_coh.cpp:239`(`data_input_buff` 输出到 `point_recv` 的循环之后) | `RDP_RECORD_COHESION_TWS` | `RdpRecPointCohesionTWS` | 两帧滑窗凝聚后进入关联的点迹 |
| ③ | TAS 凝聚后点迹 | `dot_coh_tas.cpp:82`(输出到 `point_recv_tas` 的循环之后) | `RDP_RECORD_COHESION_TAS` | `RdpRecPointCohesionTAS` | 单帧凝聚后的 TAS 点迹 |
| ④ | 临时航迹 | `track_init.cpp:117`(TWS 圈内**转为可靠航迹之前**,清空点迹之后、`tmp_track_to_trust_track()` 之前,双层遍历 `temp_track` 收集) | `RDP_RECORD_TEMP_TRACKS` | `RdpRecTempTrack` | 每条临时航迹的每个点一行,含 `temp_track_id` / `point_id` / `circle_id`;记录时机于 e982804 前移,因此表中包含**本圈参与起始判定但可能随即起批转走**的临时航迹(原先在消亡之后记录会丢失这部分数据) |
| ⑤ | 可靠航迹 | TWS:`track_asso.cpp:174`(输出分支循环结束后);TAS:`track_asso_tas.cpp:134`(输出循环结束后) | `RDP_RECORD_TRUST_TRACKS` | `RdpRecTrustTrack` | 按实体字段定义落库内部 `Trust_Track`(含 IMM 的 `X`/`P`/`X1`~`X3`/`P2`/`P3`/`u`,数组经 `arrayToString()` 存为文本列)。**两通道共用一张表**,用 `Track_Mode`(TWS 0 / TAS 1)区分;均只记录**本圈有输出**的航迹,`track_init.cpp` 起批路径未埋此点。质量相关字段(`Quality` / `Unupdated_time` / `Point_sum` / `Valid_point_sum`)自 1ffb287 起已加入该实体,可直接 SQL 分析;注意 TAS 行不维护质量(值停留为起批初值),见 README 11.2 |
| ⑥ | 点航关联信息 | TWS:`track_asso.cpp:581`(未通过门限的候选)、`:743`(被最近邻取中的候选)、`:761`(因航迹/点迹已被消费而删表的候选),函数末尾 `:861` flush 剩余;TAS:`track_asso_tas.cpp:382`(未通过门限的候选)、`:524`(被取中候选及其余候选),函数末尾 `:574` flush 剩余 | `RDP_RECORD_ASSO_INFOS` | `RdpRecAssoInfo` | 每个候选一行(`d1`/`d2`/`d3`/`d_min`、`delta_T`/`delta_h`/`R`/`VT`、`thd_d`/`thd_h`/`thd_r`),以 `min_d_flag` 标记是否被最近邻取中、以 `tas_flag`(点迹 `point_type`,1 TAS / 0 TWS)区分来源通道,可用于复现关联决策。收集到 `RDP_RECORD_ASSO_CHUNK_ROWS`(默认 131072 行)即分块入队,限制单圈处理线程内存峰值 |
> 表清单即上表 ①②③④⑤⑥ 六张在用表。① 在判圈判定之前入队,因此即使本帧触发判圈/返回,输入点迹也不会漏记;②③④⑤ 在收集循环结束后一次入队;⑥ 按分块上限多次入队并在函数返回前 flush。
**链路串联**:①→②/③(点迹 → 凝聚后点迹)按 `point_id` 关联;②/③→④/⑤ 按 `point_id` 与 `circle_id` 关联;④→⑤ 通过起批时透传的 `track_id`(取自临时航迹末点的 `temp_track_id`)关联;⑤→⑥ 按 `track_id` 与 `point_id` 关联。TWS 与 TAS 可先用 `RdpRecPoint.point_type`、`RdpRecTrustTrack.Track_Mode`、`RdpRecAssoInfo.tas_flag` 三处之一区分通道。`circle_id` 在 TWS 每判满一圈(由配置项 `scan_mode` 选择判据:0 相扫为波位号不再增大、1 机扫为波位号 0,见 README 7.2)、TAS 每帧处理结束时自增,可用来划分「第几圈/第几帧」。
**字段速查**(字段顺序即建表列顺序;首列均为 `int64_t id` 自增主键):
- `RdpRecPoint` / `RdpRecPointCohesionTWS` / `RdpRecPointCohesionTAS`(三者字段一致):`point_id`、`frame_id`、`circle_id`、`range`、`azimuth`(弧度)、`velocity`、`amplitude`、`height`、`SNR`、`RCS`、`encoder_value`、`amp_asv_point`、`prn_fftn_point`、`CPI_time`、`GNSS_time`、`use_flag`、`azimuth_beam_index`、`pitch_beam_index`、`PRF_index`、`freq_index`、`point_type`、`point_section_asso`;
- `RdpRecTempTrack`:`temp_track_id`、`point_id`、`circle_id`、`X`、`P`(文本)、`r`、`azi`、`vr`、`height`、`SNR`、`RCS`、`amplitude`、`CPI_time`、`GNSS_time`、`d`、`buff_round`、`temp_track_section_idx`、`asso_flag`、`pitch_num`;
- `RdpRecTrustTrack`:`track_id`、`point_id`、`circle_id`、`Track_Index`、`Range`、`Azimuth`、`Elevation`、`Velocity`、`Amplitude`、`Height`、`T_track`、`GNSS_time`、`Track_Sum`、`Point_Index`、`Track_Mode`、`Track_Update_Flag`、`Extrapolate_round`、`point_flag`、`approach_flag`、`manual_delete_flag`、`manual_tracking_flag`、`associate_point_number`、`primary_target_flag`、`Target_Type`、`Track_section_idx`、`range_point`、`azi_point`、`elev_point`、`vr_point`、`prf_point`、`point_type`、`snr_point`、`amp_asv_point`、`prn_fftn_point`、`RCS`、`pitch_num`、`X`、`P`、`X1`、`X2`、`X3`、`P2`、`P3`、`u`(后 8 项为文本);
- `RdpRecAssoInfo`:`point_id`、`track_id`、`circle_id`、`d1`、`d2`、`d3`、`d_min`、`point_index`、`track_index`、`delta_T`、`delta_h`、`R`、`VT`、`thd_d`、`thd_h`、`thd_r`、`min_d_flag`。
- `RdpRecTempTrack`:`temp_track_id`、`point_id`、`circle_id`、`X`、`P`(文本)、`r`、`azi`、`vr`、`height`、`SNR`、`RCS`、`amplitude`、`quality`(航迹质量,0~1,1ffb287 新增)、`CPI_time`、`GNSS_time`、`d`、`buff_round`、`temp_track_section_idx`、`asso_flag`、`pitch_num`;
- `RdpRecTrustTrack`:`track_id`、`point_id`、`circle_id`、`Track_Index`、`Range`、`Azimuth`、`Elevation`、`Velocity`、`Amplitude`、`Height`、`Quality`(航迹质量,0~1,1ffb287 新增)、`T_track`、`GNSS_time`、`Track_Sum`、`Unupdated_time`(未更新累计毫秒,1ffb287 新增)、`Point_Index`、`Point_sum`、`Valid_point_sum`(两项均为 1ffb287 新增)、`Track_Mode`、`Track_Update_Flag`、`Extrapolate_round`、`point_flag`、`approach_flag`、`manual_delete_flag`、`manual_tracking_flag`、`associate_point_number`、`primary_target_flag`、`Target_Type`、`Track_section_idx`、`range_point`、`azi_point`、`elev_point`、`vr_point`、`prf_point`、`point_type`、`snr_point`、`amp_asv_point`、`prn_fftn_point`、`RCS`、`pitch_num`、`X`、`P`、`X1`、`X2`、`X3`、`P2`、`P3`、`u`(后 8 项为文本);
- `RdpRecAssoInfo`:`point_id`、`track_id`、`circle_id`、`d1`、`d2`、`d3`、`d_min`、`point_index`、`track_index`、`delta_T`、`delta_h`、`R`、`VT`、`tas_flag`、`thd_d`、`thd_h`、`thd_r`、`min_d_flag`。
各表的字段为**调试用途**,按第 5 节增删即可,不影响框架。注意记录点中的字段错误只在启用记录功能的构建中才会被检查(关闭时宏丢弃实参)。
各表的字段为**调试用途**,按第 6 节增删即可,不影响框架。注意记录点中的字段错误只在启用记录功能的构建中才会被检查(关闭时宏丢弃实参)。
**两点使用提醒**:
**四点使用提醒**:
- `RdpRecTrustTrack::Track_Update_Flag` **恒为 0**:写库发生在输出循环内,而该循环入口已先把标志清零(`track_asso.cpp` 先置 0、再输出、最后记录)。判断「某航迹本圈是否更新」应改用该表是否出现对应 `track_id` / `circle_id` 的记录,或改用 `RdpRecAssoInfo::min_d_flag`。
- `Range` / `Azimuth` / `Elevation` / `Velocity` 四个内部快照**只在航迹当圈有输出时被回填**,长期未输出(外推超限)的航迹在表中为 0 或旧值。
- `RdpRecTrustTrack::Track_Update_Flag` **恒为 0**(TWS 与 TAS 两处记录点相同):写库发生在输出分支内,而该分支入口已先把标志清零(`track_asso.cpp` / `track_asso_tas.cpp` 均先置 0、再输出、最后收集)。判断「某航迹本圈是否更新」应改用该表是否出现对应 `track_id` / `circle_id` 的记录,或改用 `RdpRecAssoInfo::min_d_flag`。
- `Range` / `Azimuth` / `Elevation` / `Velocity` 四个内部快照**只在航迹当圈有输出时被回填**(两通道均在收集前回填),长期未输出(外推超限)的航迹在表中为 0 或旧值。
- **TAS 关联信息的三个字段为占位值**:`track_asso_tas.cpp` 收集的候选把 `R` / `VT` 置 0(TAS 关联只用统计距离与高度两个门限),并把 `thd_r` 恒置 true;`track_index` 取 TAS 目标批号 `tas_track_idx`(不是航迹表下标,TWS 行才是下标)。分析 TAS 行时不要按 TWS 的语义解读这三项。
- **记录表未含航迹指纹(V1.5.9)**:`Trust_Track::Track_fingerprint`(16 bit,TAS 输入/输出用它定位航迹)**尚未加入** `RdpRecTrustTrack` 实体;如需按指纹复盘 TAS 命令,按第 6 节配方给实体加字段并在 `rdp_record.cpp` 注册即可(同 `Quality` 的加法)。
## 7. 查询与分析
## 8. 查询与分析
```python
# 取最近一次运行的记录库并查看表结构、行数与抽样数据(Python 自带 sqlite3 即可)
@@ -175,21 +277,59 @@ print(con.execute("""
order by p.id desc limit 10
""").fetchall())
# 查看某圈的关联决策(min_d_flag=1 为最终取中的候选)
# 查看某圈的关联决策(min_d_flag=1 为最终取中的候选;tas_flag 区分通道)
print(con.execute("""
select track_id, point_id, d_min, delta_T, thd_d, thd_h, thd_r, min_d_flag
select track_id, point_id, d_min, delta_T, tas_flag, thd_d, thd_h, thd_r, min_d_flag
from RdpRecAssoInfo where circle_id = (select max(circle_id) from RdpRecAssoInfo)
order by min_d_flag desc, d_min asc limit 20
order by tas_flag, min_d_flag desc, d_min asc limit 20
""").fetchall())
# TAS 航迹与 TWS 航迹共表,用 Track_Mode 区分(0 TWS / 1 TAS)
print(con.execute("""
select track_id, circle_id, Track_Index, Track_Mode, Range, Velocity, Extrapolate_round
from RdpRecTrustTrack where Track_Mode = 1 order by id desc limit 10
""").fetchall())
```
也可用 DB Browser for SQLite 直接打开 db 文件;同一 db 内所有表一一对应各记录实体。
## 8. 注意事项与已知限制
查询时的三点注意:
- **`id` 是 SQLite 的主键/ROWID 自增列,单调但不保证连续**(批量插入与失败回滚都会产生空洞),排序用 `id` 没问题,但不要用它推断「第几行被记录」;
- **跨表不能按 `id` 判断先后**:各表有独立的 id 序列,且写线程按表分别提交,跨表写入顺序不保证;跨表关联请用 `point_id` / `track_id` / `circle_id`;
- 正常 `Destroy()` 后 WAL 已 `checkpoint(TRUNCATE)`,目录下通常只剩一个 `.db`;若进程异常退出,目录中可能残留 `-wal`/`-shm` 文件,需用 SQLite(而不是直接读文件)打开查看,尾部记录可能缺失。
## 9. 注意事项与已知限制
- **交付构建不要开启**:`Release` + `RDP_DATA_RECORD=ON` 仅用于临时排查,会带来 sqlite3 编译与运行时写入开销;正式交付保持 `AUTO`/`OFF`;
- **Release 下记录点不做类型检查**:关闭时宏丢弃实参,因此记录点里的字段拼写/类型错误只在启用记录功能的构建中才会暴露——新增记录点后请至少构建一次开启记录功能的配置;
- **异步尾部可能丢失**:记录是异步落库的,进程崩溃、被强杀或**未调用 `Destroy()`** 时,队列中尚未写库的数据与 WAL 尾部都可能丢失;正式采集结束时务必调用 `Data_Process_Factory::Destroy()` 让 `Shutdown()` 排空;
- **队列满会整批丢弃**:写线程跟不上(慢盘、杀软实时扫描、CPU 争抢)时,超出 `RDP_RECORD_QUEUE_CAP_BYTES`/`RDP_RECORD_QUEUE_MAX_BATCHES` 的**整批**数据被丢弃,`stats().dropped_rows` 可查,日志按每 1024 行限频提示;这是「实时性优先」的刻意取舍,不是缺陷;
- **记录期间关闭记录功能**:`RDP_RECORD_ENABLE` 是编译期开关,运行时无法关闭;若宿主需要停止记录,请调用 `Destroy()`(排空并关库)后不再 `GetB()`,`Shutdown()` 之后的写入静默忽略;
- **⑥ 未裁剪,仍是 M×N**:关联信息按每个候选一行记录;200 目标/150 点压测场景单圈约 12.3 万行,判圈帧 p99 ≈ 13~24 ms(超 5 ms 验收阈值),受控 50 目标/50 点场景最大 5.96 ms。若对判圈帧实时性有硬要求,需后续实现 P0-a(只记录被取中候选/按需裁剪),本轮未做;
- **WAL 需要本地盘**:WAL 依赖共享内存与文件锁,网络盘/不支持的文件系统上可能设置失败(代码会回退默认 journal 并提示);记录库本身也可能很大(60 圈约 1.06 GB),采集时需预留磁盘;
- **写线程是后台优先级**:Windows 下写线程 CPU 优先级为 BELOW_NORMAL,避免与雷达处理线程争抢;磁盘慢时记录先丢批而不是拖慢处理;
- `NaN`/`Inf` 会由 SQLite 存为 `NULL`(`sqlite3_bind_double` 行为),分析时需注意;
- 记录库是调试产物:进程异常退出(未调用 `Destroy()`)时,`synchronous=OFF` 下可能丢失最后若干条记录;
- **不保证跨表顺序、`id` 不连续**:见第 8 节;
- **`RDP_RECORD`/`RDP_RECORD_BATCH` 会阻塞**:这两个遗留宏在调用线程内同步写库(与写线程经 `db_mtx` 串行化),不要在实时性敏感的路径上使用;
- IntelliSense 可能对 `rdp_record.cpp` 按 `c_cpp_properties.json` 的 C++17 提示(该文件实际按 C++20 编译,`build/<...>/compile_commands.json` 中带有 `/std:c++20`),以实际构建结果为准;
- `third_party/ormpp/` 为上游原样内置(ormpp 0.2.0 / iguana 1.0.5 / SQLite 3.49.1,Apache-2.0 与公有领域),升级方式与校验值见该目录 `UPSTREAM.md`。
## 10. 验证与性能参考(2026-10-20 单机实测)
> 以下为优化实施后的实测结果,原始日志与可重复脚本位于 `build/analysis/verify/`(被 `.gitignore` 忽略,
> 不随交付;脚本用法见该目录 README.md)。数据随机器、磁盘与杀软环境变化,仅作量级参考。
| 项目 | 结果 |
|---|---|
| 构建 | 8 种配置(PS/MS × Release/Debug × REC_OFF/REC_ON,机扫/相扫合并前;合并后为 4 种配置)均 **0 warning、0 error** |
| 普通帧开销 | dup 场景(6 圈/200 目标/每帧 150 点/100 波位)REC_ON vs REC_OFF:每帧 `total` 新增 **p99 ≈ 0.09~0.11 ms**(验收阈值 ≤ 1 ms,PASS) |
| 判圈帧开销 | 同场景判圈帧 `track` 新增 **中位 ≈ 2.9~4.1 ms、p99 ≈ 13~24 ms**(多次验收运行)——已超 5 ms 验收阈值,因未采用 P0-a(单圈约 12.3 万关联候选),属压测上限;受控 50 目标/50 点场景判圈帧新增中位约 3 ms、**最大 5.96 ms** |
| 端到端耗时 | 同场景 600 帧:ON wall ≈ 2.1 s vs OFF wall ≈ 1.8 s |
| 行数一致性 | 与改动前完全一致:`RdpRecPoint` 90,000 / `RdpRecPointCohesionTWS` 4,170 / `RdpRecAssoInfo` 211,584 / `RdpRecTempTrack` 8,330 / `RdpRecTrustTrack` 304(合计 **314,388**) |
| 库健康度 | `PRAGMA integrity_check = ok`;DDL 无 `AUTOINCREMENT`;`journal_mode = wal`;5 张表 `id` 严格递增;`dropped_rows = 0` |
| 60 圈长跑 | 约 43.5 s、**9,987,370 行**、`dropped_rows=0`、`write_errors=0`、队列峰值 ≈ 19.8 MB(上限 64 MB)、DB 约 **1.06 GB** |
| 故障注入 | 记录目录不可建(同名普通文件占位):`Init()` 静默失败、驱动正常完成(exit=0、无崩溃、未产生 db);小队列测试构建(cap=1 KB)丢弃 314,388 行且 `dropped_rows` 有计数、处理正常 |
| 重连 | `Destroy()` → `GetB()` 生成两个独立 db 且均正常写入(同秒文件名冲突自动追加后缀) |
> 注:上表实测于 2026-10-20(性能优化验收),早于 1ffb287 增加航迹质量字段;新增列不改变行数,但当时的表结构不含 `quality` / `Quality` / `Unupdated_time` / `Point_sum` / `Valid_point_sum`,如需按这些列验收请重新跑一轮记录。
+741
View File
@@ -0,0 +1,741 @@
# 数据记录调试功能(RDP_DATA_RECORD)性能瓶颈分析与实时性优化报告
> **项目**:雷达数据处理动态库 `data_process_class_dll`(V1.5.8.PS / V1.5.8.MS,分支 `feature`,提交 `3dee734`)——本报告为**机扫/相扫合并前**的快照;合并后版本串为 `V1.5.9.GENERIC`,构建目录与产物路径见 README 4.3。
> **问题**:Release 构建下,`RDP_DATA_RECORD=ON` 开启数据记录调试功能后,数据处理不能实时完成;`RDP_DATA_RECORD=OFF` 时满足实时性。
> **本报告范围**:定位性能瓶颈、给出可落地的优化措施与验收标准。**本阶段不修改任何业务代码**——报告中的代码改动均为"建议方案",未实施(业务代码/CMake/第三方均无改动,仅新增本报告文件)。
> **方法**:源码走读 + 数据量建模 + Release 产物微基准 + `REC_ON`/`REC_OFF` 双 DLL 端到端 A/B 实测。
> **证据产物**(均为本报告的一次性分析产物,位于被 `.gitignore` 忽略的 `build/` 下):
> - 记录点与行数模型:`build/analysis/hotpath_findings.md`
> - ormpp/SQLite 单条写入链路:`build/analysis/ormpp_sqlite_findings.md`
> - 微基准:`build/analysis/bench_micro.md`、`build/perf_bench_micro/`
> - 端到端 A/B:`build/analysis/bench_pipeline.md`、`build/perf_bench_pipeline/`
> - 优化实施与验收结果:本报告第 11 节;验收脚本与日志:`build/analysis/verify/`
> - 本报告:`docs/data_record_perf_report.md`
---
## 0. 结论摘要(TL;DR)
1. **根因不是"Release 没优化好",而是记录路径的设计是"在雷达处理线程上逐条同步写 SQLite"**:12 个记录点全部是就地构造实体 + 单条 `Write()`,每条记录 = 一次**独立隐式事务**(创建/写/删除回滚日志 + 文件锁 + 脏页写回)。CPU 编译优化对文件系统/锁/事务这些开销无效,所以 Release 下依旧卡。
2. **实测单行写入成本 ≈1.1~1.3 ms(mean)、p50≈0.73~0.78 ms、p99≈5~6.3 ms**(本机:i7-10700K + NTFS 机械盘数据卷 + Defender 实时防护开启;3 次独立运行中位数,单行最大尖峰 81~496 ms)。**其中 rollback journal 文件的创建/写入/删除约占 96%~97%**:同一事务路径下把 journal 换成内存(MEMORY)或 WAL 后每行降到 38.7 µs / 17.2 µs(原生文件操作对照:空文件 create+close+delete ≈0.15~0.18 ms、写 4 KB 后删除 ≈0.29~0.31 ms)。`synchronous=OFF` 只去掉 fsync,不改变"每行一个事务"。
3. **端到端 A/B 实测**(同一合成场景,600 帧 × 150 点/帧):
- 仅输入点迹记录路径(未起批):`REC_OFF` 每帧合计 **0.21 ms**,`REC_ON` 每帧 **150.1 ms**(≈722 倍);每圈 **0.14 s → 16.8 s**(≈120 倍);仅"每帧 150 行输入点迹"就贡献约 144 ms/帧,**单帧即超 10 ms 实时预算 14 倍**。
- 含航迹场景(186 条可靠航迹,关联路径真实覆盖):`REC_OFF` 每帧 **0.269 ms** → `REC_ON` **218.03 ms(811 倍)**;判圈帧 `track_process` **318 ms → 59.2 s(max 170.3 s,227 倍)**;每圈 **0.345 s → 81.05 s(235 倍)**;关联候选记录 8.7 万~12.3 万行/圈、占判圈帧 99%,**这是现场"不能实时"的直接原因**。
4. **比"逐条事务"更危险的是数据量本身**:TWS 点航关联信息按**每圈 `M×N` 行**记录(`M`=可靠航迹数、`N`=本圈关联批凝聚点数,`N≈99×每帧凝聚点数`)。稀疏场景约 1.8 万行/圈,中等场景约 44.6 万行/圈,稠密场景可达 **5.4×10⁶ 行/圈(约 0.5 GB/圈)**——即使写库成本降到零,磁盘吞吐也支撑不了,**必须对关联候选做裁剪/采样**。
5. **第三瓶颈是处理线程上的实体构造**:`RdpRecTrustTrack` 每行要执行 **135 次 `std::ostringstream`(实测 133.6 µs/行、≈1.1 KB 文本)**,`RdpRecTempTrack` 每行 20 次(19.4 µs/行)。批处理只省事务,**省不掉这些构造**(批量后构造占 TrustTrack 端到端成本 94%、TempTrack 81%);必须把构造搬到后台线程或改列类型。
6. **优化收益已实测**:改为批量事务(框架已有 `RDP_RECORD_BATCH`/`Write(vector)` 能力,当前调用点为 0)后,DB 侧每行从 ~1.1 ms 降到 **3.8~8.8 µs(批 1000,按实体;约 95~336 倍)**;端到端(含构造)Point/Asso ≈207~335×、TempTrack ≈52×、TrustTrack ≈10×。WAL+`synchronous=OFF` 可把单条 autocommit 从 1120 µs 降到 17.2 µs(~55~65 倍,但仍远不够高行数场景);每帧小批量(≤150 行)时 WAL 还有 1.8× 价值(批 100:DELETE 9.83 µs/行 → WAL 5.53 µs/行);**不要用 `synchronous=NORMAL`**(本机机械盘 fsync ≈31 ms,checkpoint 抖动使 NORMAL 相对 `sync=OFF` 慢 8~12×)。
7. **推荐方案(分层)**:
- **P0-a 关联信息裁剪(决定性)**:点航关联只记录"被最近邻取中的候选 + 少量近失候选/采样",把 ⑥ 从 `M×N` 降到 `O(M+N)`;
- **P0-b 全量批处理**:①~⑤、TAS ⑥ 改为"帧末/圈末收集 → 一批一个事务",直接复用现有批量接口;
- **P0-c 异步写线程(实时性保证)**:处理线程只把 POD 快照压入有界队列,后台线程负责序列化 + 批量入库,队列满时丢弃并计数——把磁盘抖动与处理线程彻底解耦;
- **P1 补充**:信任航迹数组列改 blob/后台序列化、临时航迹只记增量、WAL/`synchronous`/`cache_size`/去 `AUTOINCREMENT`、失败熔断、记录库轮转;环境上排除 Defender 扫描目录、避免机械盘。
- 目标:开启记录功能后,**处理线程每帧新增耗时 p99 ≤ 1 ms、判圈帧 ≤ 5 ms、零丢帧**;交付构建仍保持 `OFF` 零开销。
8. **交付构建不受影响**:`RDP_DATA_RECORD=OFF` 时记录模块不参与编译、宏为空操作(`rdp_record.h:44-45`、`CMakeLists.txt:118-158`),DLL 体积 182,784 → 1,414,656 字节的差异只存在于 `ON` 构建。报告中的改动全部位于 `#ifdef RDP_RECORD_ENABLE` 保护的调试代码与记录模块内部,**不进入交付产物**。
---
## 1. 问题与背景
### 1.1 现象
| 配置(均为 Release;使用当时的 X256_PS 构建,现机扫/相扫已合并,等价产物在 `build/Release/REC_*`) | 现象 |
|---|---|
| `RDP_DATA_RECORD=OFF`(交付/AUTO 默认) | 数据处理满足实时性 |
| `RDP_DATA_RECORD=ON`(显式开启,用于 Release 现场排障) | 数据处理不能实时完成,且负载越密(点迹/航迹越多)越慢,最终表现为处理节拍被拖垮、数据积压 |
关键点:两套构建的**雷达处理算法源码完全相同**,差异只有 `RDP_RECORD_ENABLE` 宏是否定义、以及 `rdp_record.lib`(含 SQLite)是否链接进 DLL。因此问题一定出在"记录路径的运行期行为",而不是算法或编译优化级别。
### 1.2 数据记录功能与开关(为什么 OFF 能实时)
- 记录实现:`data_process_class_dll/rdp_record.h`、`rdp_record.cpp`、`rdp_record_entity.h`;
- 构建开关:CMake 缓存变量 `RDP_DATA_RECORD`(`AUTO`/`ON`/`OFF`,`CMakeLists.txt:34-57`),启用时定义 `RDP_RECORD_ENABLE` 并编译 `rdp_record` 静态库(`CMakeLists.txt:121-157`);
- 关闭时:`RDP_RECORD(...)` / `RDP_RECORD_BATCH(...)` 展开为 `((void)0)`(实参不求值,`rdp_record.h:42-45`),记录模块源码完全不参与编译(`CMakeLists.txt:118-158` 的 `if(RDP_RECORD_ENABLED)` 才创建/链接 `rdp_record` 目标)。**OFF 的实时性来自"代码不存在",不是"运行期判断跳过"**。
- 运行期:`Data_Process_Factory::GetB()` → `rdp_record::Init()` 建目录/开库;`Destroy()` → `Shutdown()`(`data_process_class_dll.cpp:18-51`);单连接 + 一把全局互斥锁(`rdp_record.cpp:35-44,183`);写入失败静默降级(`rdp_record.cpp:188-195`)。
- 功能定位与已知限制(`docs/data_record_debug.md:15,57,197-204`):**不做异步、不做数据裁剪、不追求多线程写入性能**,默认只在 Debug 启用。这正是本次问题的设计层根源——该功能按"调试期低频使用"设计,而现场需要在 Release 下开启它排障。
### 1.3 本报告的复现方式
由于没有宿主主程序,本次用两条独立证据链复现与量化:
1. **微基准**(`build/perf_bench_micro/`):直接链接已有 Release `REC_ON` 产物 `build/Release/REC_ON/lib/rdp_record.lib`(本报告测量时位于旧布局 `build/X256_PS/Release/REC_ON/lib/`;机扫/相扫合并后为 `build/Release/REC_ON/lib/`),调用与业务完全相同的 `rdp_record::Write<T>()`,测单条/批量/不同 SQLite 配置的每行成本;并单独测实体构造(`arrayToString`)成本。
2. **端到端 A/B**(`build/perf_bench_pipeline/`):编写只使用公开 DLL 接口的合成数据驱动程序,分别加载 `REC_OFF` 与 `REC_ON` 两个 `data_process_class_dll.dll`,按宿主调用序列(`data_preprocess` → 按返回的 `model` 调 `track_process`)投喂同一份合成 TWS 数据,用 `QueryPerformanceCounter` 逐帧计时。
> 证据链互相印证:微基准单条写入 mean 约 1.1~1.3 ms/行(p50 约 0.73~0.78 ms);首轮端到端 A/B 全运行 103,374 行 / ≈97.6 s 增量 ≈ **0.94 ms/行**;含航迹最终 A/B 为 314,388 行 / 426.7 s ≈ **1.39 ms/行**(大库/多表/页缓存压力下略高),三条口径一致。
---
## 2. 分析方法、环境与证据强度
### 2.1 方法总览
| 方法 | 内容 | 产物 |
|---|---|---|
| 源码走读 | 12 个 `RDP_RECORD` 记录点的循环嵌套、触发频率、行数公式;ormpp/SQLite 单条写入全链路(file:line) | `hotpath_findings.md`、`ormpp_sqlite_findings.md` |
| 数据量建模 | 以 `M`(航迹数)、`N`(关联批点数)、`p`(每帧点数)、`B`(每圈波位数)构造每圈行数公式与场景表 | 本报告第 5 节 |
| 微基准 | 单条 vs 批量写入、SQLite 事务/journal/WAL/语句复用/AUTOINCREMENT 对比、实体构造与 `arrayToString` 成本 | `bench_micro.md` |
| 端到端 A/B | `REC_ON` vs `REC_OFF` 双 DLL,同机同场景逐帧计时(含起批成功、关联路径覆盖的场景) | `bench_pipeline.md` |
| 记录库校验 | 对 A/B 产生的 db 做行数/体积/`integrity_check` 统计 | `build/analysis/count_db_rows.py` |
### 2.2 测试环境
| 项 | 值 |
|---|---|
| 主机 | Intel Core i7-10700K(8C16T,3.8 GHz),31.8 GB 内存 |
| OS | Windows 10 专业版 19045(x64) |
| 编译器/构建 | MSVC 2022 14.44(v143),CMake `Release`(`/O2 /Ob2 /DNDEBUG /MD`),NMake 单配置 |
| 被测 DLL | `REC_OFF` 182,784 B / `REC_ON` 1,414,656 B(均已有 Release 产物,未重建主工程) |
| 磁盘 | 工程与记录库位于 E:(WDC WD20EZAZ,**SATA 机械盘**);系统盘为 SATA SSD |
| 安全软件 | Windows Defender:`RealTimeProtectionEnabled=True`、`OnAccessProtectionEnabled=True` |
| SQLite | 3.49.1 amalgamation,默认编译选项(`SQLITE_THREADSAFE=1`、`cache_size=-2000`、`page_size=4096`、无额外 `SQLITE_*` 宏) |
| 计时 | `QueryPerformanceCounter`(10 MHz);进程绑核 + `HIGH_PRIORITY_CLASS`;预热 300 ms;首圈作为预热不计入统计 |
### 2.3 证据强度与局限(先声明,避免误读)
- **强证据**:单行/批量每行成本、SQLite 各配置对比、实体构造成本、DLL 体积、代码位置与行数公式。
- **中等证据**:端到端 A/B 的绝对毫秒数。合成场景的"每帧 150 点、100 波位/圈、200 目标"是**压力场景**,绝对耗时与真实雷达负载不同;但 **ON/OFF 的比值、每行成本、`M×N` 的行数规律**可迁移。A/B 已覆盖"未起批(仅点迹路径)"与"186 航迹(关联路径)"两种形态;只差"150 个独立点迹不经凝聚"的稠密形态(按 1.39 ms/行外推,未实跑)。
- **不在本次证据内**:真实宿主的多线程调用行为(README 的已知问题 BUG-03 声明"接口线程模型未定义,宿主应单线程调用",`README.md:1020`);现场磁盘/Defender 的真实配置;真实场景的 `M`、`N`、凝聚存活率;TAS 路径与 `Beam_Ctrl` 未做端到端实测(记录点同构,每行成本结论适用)。
- 因此本报告的优化措施同时给出"**本机实测收益**"与"**需现场复测校准**"两类数据;第 9.3 节给出复测方法。
---
## 3. 记录链路全貌:12 个同步单条写入点
### 3.1 调用链
```
宿主(单线程,按 README BUG-03 约定)
└─ data_preprocess(DataRev[150]) // 每帧
└─ 逐点循环 → RDP_RECORD(RdpRecPoint(...)) // data_process.cpp:60 / :132
└─ track_process(..., model) // 每帧或每圈
├─ model==2: dot_coh_process_buff()
│ └─ RDP_RECORD(RdpRecPointCohesionTWS) // dot_coh.cpp:230
├─ model==1: track_asso_process()
│ ├─ model_filter() 双层循环(航迹×点迹)
│ │ ├─ 未过门限: RDP_RECORD(RdpRecAssoInfo) // track_asso.cpp:554
│ │ └─ 最近邻消费: RDP_RECORD(RdpRecAssoInfo) // :710 / :722
│ ├─ 输出循环: RDP_RECORD(RdpRecTrustTrack) // :163
│ └─ track_init_process_logic()
│ └─ 临时航迹双层遍历: RDP_RECORD(RdpRecTempTrack) // track_init.cpp:114
└─ model==3(TAS): dot_coh_tas → track_asso_tas
└─ RDP_RECORD(...) // dot_coh_tas.cpp:73, track_asso_tas.cpp:122/363/499
RDP_RECORD 展开(ON 时):
构造实体(含 arrayToString)→ ::rdp_record::Write(const T&)
→ 全局 std::mutex 加锁
→ ormpp 单条 insert:重拼 INSERT SQL → sqlite3_prepare_v2 → 逐列 bind → sqlite3_step(隐式事务提交)→ finalize
→ 解锁,返回雷达处理线程
```
**关键事实:整条链路没有队列、没有线程边界、没有事务聚合**。每一条记录都同步占用雷达处理线程,雷达处理必须等磁盘事务完成才能继续(`data_process.cpp:18/155` 是同步接口)。
### 3.2 记录点清单(6 张在用表 / 12 个调用点 / 0 个批量调用)
| 表 | 埋点 | 所在循环 | 触发频率 | 每圈/每帧行数 |
|---|---|---|---|---|
| `RdpRecPoint`(①输入点迹) | `data_process.cpp:60`(TWS)、`:132`(TAS) | `data_preprocess` 逐点循环 | 每帧 | TWS `B·p`(≤15,000/圈);TAS ≤150/帧 |
| `RdpRecPointCohesionTWS`(②) | `dot_coh.cpp:230` | 凝聚输出循环 | 每圈 `B−1` 次(model=2) | ≈`N` 行/圈 |
| `RdpRecPointCohesionTAS`(③) | `dot_coh_tas.cpp:73` | TAS 凝聚输出循环 | 每 TAS 帧 | ≤150/帧 |
| `RdpRecTempTrack`(④) | `track_init.cpp:114` | 临时航迹双层遍历 | 每圈 1 次(model=1) | `ΣL`(全量快照,≈1~3×未关联点数) |
| `RdpRecTrustTrack`(⑤) | `track_asso.cpp:163`(TWS)、`track_asso_tas.cpp:122`(TAS) | 输出循环(`Track_Update_Flag==1`) | 每圈/每 TAS 帧 | ≤`M`≤500/圈;TAS ≤1/帧 |
| `RdpRecAssoInfo`(⑥) | `track_asso.cpp:554/710/722`、`track_asso_tas.cpp:363/499` | 关联候选双层循环(航迹×点迹) | 每圈 1 次(model=1) | **`M×N` 行/圈(精确等于候选对数)**;TAS ≤150/帧 |
> 全仓库 `RDP_RECORD_BATCH` 调用点为 **0**(`grep` 验证;宏与批量 `Write(vector)` 早已实现但从未被业务使用,`rdp_record.h:41`、`rdp_record.cpp:198-225`)。
### 3.3 频率与时间窗(决定了行数量级)
- TWS:每帧一次 `data_preprocess`;一圈 `B` 个波位(`MAX_BEAM_NUM=100`,`parameters.h:87/138`;实际圈长由宿主波位编排决定);每圈 **1 次 model=1**(关联/消亡/起始)+ **B−1 次 model=2**(滑窗凝聚)。
- **TWS 的关联批是一整圈的累积**:`point_recv` 在整圈内只增不清(`dot_coh.cpp:229` → `track_asso.cpp:29-68` → `track_init.cpp:60/108`),model=1 时关联的是"上一圈全部凝聚点",因此 `N ≈ (B−1)×每帧凝聚点数`,而不是单帧的 ≤150 点。这是行数量级被低估的常见原因。
- 注:`README.md:445` 的流程图把 TWS 凝聚画在 model=1 分支下,与代码不符;代码中凝聚只在 model=2 调用(`data_process.cpp:163-168`)。
- TAS:每来一帧处理一帧,`point_recv_tas` 每帧清空,关联仅针对单一批号航迹(`track_asso_tas.cpp:263-283`),记录量比 TWS 小 1~2 个数量级。
- 实时预算(本报告采用的参考值,需现场按实际 CPI 校准):帧周期 **10 ms**(100 Hz CPI)、圈周期 **1 s**(100 波位 × 10 ms)。记录功能新增的耗时直接叠加在这两个预算上。
---
## 4. 瓶颈一:每条记录一次同步事务(I/O 放大)
### 4.1 单行写入链路拆解(代码事实)
单条 `rdp_record::Write(const T&)`(`rdp_record.cpp:174-196`)每次调用都完整执行:
```
状态检查 → std::lock_guard<mutex>(全模块一把锁)
→ PrepareWrite<T>(静态 guard,建表只一次)
→ ormpp::dbng::insert → sqlite.hpp:834-856 insert_or_update_impl:
generate_insert_sql<T>() // 每次重新拼 SQL 字符串(utility.hpp:438-521)
sqlite3_prepare_v2() // 每次重新解析/编译(sqlite.hpp:842)
bind 逐列(22/22/19/44/18 次,id 自增列跳过) // sqlite.hpp:602-608, 645-696
sqlite3_step() // ← 隐式事务提交发生在这里
sqlite3_finalize() // 释放语句对象
→ 失败时每个实体仅提示一次
```
单条路径**没有任何 `BEGIN/COMMIT`**,因此每条 INSERT 处于 SQLite autocommit:**每条记录 = 一个独立隐式写事务**,在 Windows 上包含:
| 阶段 | 动作 |
|---|---|
| 加锁 | 获取 SHARED/PENDING/RESERVED/EXCLUSIVE 锁(`LockFileEx`) |
| 建 journal | 创建 rollback journal 文件(`CreateFileW`)+ 写 journal 头 |
| 写 journal | 修改页的改前镜像写入 journal |
| 写主库 | 提交时脏页写回 db 文件(表页 + `page1` + `AUTOINCREMENT` 的 `sqlite_sequence` 页) |
| 删 journal | 关闭并删除 journal 文件(`DeleteFileW`)+ 解锁 |
每次约 **10~15 个 Win32 文件/锁系统调用 + 20~40 KB 页写入**。`PRAGMA synchronous=OFF`(`rdp_record.cpp:147`)只去掉 `FlushFileBuffers`,**不减少 journal 文件的创建/写入/删除,也不减少锁**;在机械盘 + Defender 实时防护下,这条"新建文件→写入→关闭→删除"链被显著放大(SQLite 自身的 Windows VFS 都为杀软影响加了锁重试,`sqlite3.c:50289-50308`)。
### 4.2 实测:单行成本 ≈1.1~1.3 ms(mean)
微基准(Release `REC_ON` 库,本机环境,每实体按当前实现逐条写入;**3 次独立运行的中位数**,min~max 见 `bench_micro.md` §2):
| 实体(列数含 id) | 统计行数 | mean | p50 | p99 | 吞吐 | DB 体积/行 |
|---|---|---|---|---|---|---|
| `RdpRecPoint`(23) | 16,000 | **1265.5 µs** | 780.7 µs | 5643.2 µs | 790 行/s | 119.6 B |
| `RdpRecPointCohesionTWS`(23) | 16,000 | 1249.9 µs | 770.7 µs | 5073.4 µs | 800 行/s | 119.2 B |
| `RdpRecTempTrack`(20) | 16,000 | 1224.2 µs | 745.6 µs | 6027.5 µs | 817 行/s | 315.8 B |
| `RdpRecTrustTrack`(45) | 4,000 | 1326.5 µs | 761.5 µs | 6271.0 µs | 754 行/s | 1369.7 B |
| `RdpRecAssoInfo`(19) | 16,000 | 1228.7 µs | 734.8 µs | 5703.7 µs | 814 行/s | 96.5 B |
> 观察:**列数从 19 到 45、行大小从 96.5 B 到 1.37 KB,单条 p50 差异 <7%**——成本由"每条一个事务"决定,而不是 SQL/列数/序列化。mean 约为 p50 的 1.6 倍、p99 达 5~6.3 ms、单行最大尖峰 81~496 ms,说明文件系统/杀软/调度长尾直接落在同步调用栈上,这正是"偶发卡顿"的来源。
### 4.3 实测:批量事务的收益(框架已有能力,端到端 10~335×)
同一个 `rdp_record.lib`、同一数据库配置,改用 `Write(const std::vector<T>&)`(ormpp 内部一次 prepare + `BEGIN` + 逐行 bind/step/reset + `COMMIT`,`sqlite.hpp:859-902`;**若把批量实现成循环调单条 `Write` 则完全失去收益**):
| 实体(列数) | 批大小 | DB 侧每行成本 | 吞吐 | 相对单条 |
|---|---|---|---|---|
| `RdpRecPoint`(23) | 100 | 13.31 µs | 75,121 行/s | ~80~95× |
| `RdpRecPoint` | 1,000 | **3.77 µs** | 265,230 行/s | **275~336×** |
| `RdpRecPoint` | 10,000 | 2.95 µs | 339,508 行/s | 342~433× |
| `RdpRecAssoInfo`(19) | 100 | 13.90 µs | 71,921 行/s | 35~105× |
| `RdpRecAssoInfo` | 1,000 | **5.91 µs** | 169,183 行/s | **202~268×** |
| `RdpRecAssoInfo` | 10,000 | 3.68 µs | 271,844 行/s | 254~410× |
| `RdpRecTempTrack`(20,2 文本列) | 1,000 | 4.43 µs | 225,502 行/s | 208~287× |
| `RdpRecTrustTrack`(45,8 文本列) | 1,000 | 8.77 µs | 114,067 行/s | 95~154× |
- **批 1,000 是甜点**(批 10,000 提升有限);批 100 时事务固定成本仍未摊薄。
- **必须把"实体构造"也算进端到端**:单条 → 批 1000 的端到端加速比为 Point ≈335×、Asso ≈207×、TempTrack ≈52×、TrustTrack ≈10×(后两者构造占批量后成本 81%/94%,见第 6 节)——**对文本数组实体,光批处理不够**。
### 4.4 实测:SQLite 配置对比(journal/事务/语句复用/AUTOINCREMENT)
用 sqlite3 C API 在同一台机器上复现等价写入(45 列宽表 / 19/23 列窄表,每配置独立空库、20,000 行、多次运行中位数):
| 配置 | journal / sync | 批 | 语句复用 | 每行 mean | 吞吐 | 结论 |
|---|---|---|---|---|---|---|
| 当前实现等价(autocommit) | DELETE / OFF | 0 | 否 | **1120.1 µs** | 893 行/s | 基线:每行一个事务 |
| autocommit + 语句复用 | DELETE / OFF | 0 | 是 | 949.6 µs | 1,053 行/s | **无改善**(噪声内)——瓶颈不在 prepare |
| 批量 1,000 | DELETE / OFF | 1000 | 是 | **4.90 µs** | 203,991 行/s | 批处理 |
| 批量 10,000 | DELETE / OFF | 10000 | 是 | 4.55 µs | 219,761 行/s | 收益趋平 |
| 批量 1,000 + 每行重新 prepare | DELETE / OFF | 1000 | 否 | 13.46 µs | 74,270 行/s | 复用省 **7.2~10.6 µs/行** |
| autocommit + WAL + NORMAL | WAL / NORMAL | 0 | 是 | 209.7 µs | 4,770 行/s | 不要用 NORMAL |
| 批量 1,000 + WAL + NORMAL | WAL / NORMAL | 1000 | 是 | 53.68 µs | 18,630 行/s | 本机 checkpoint fsync 抖动:比 DELETE 批量差约 11×、比 WAL+OFF 批量差 8.6× |
| autocommit + WAL + OFF | WAL / OFF | 0 | 是 | **17.21 µs** | 58,116 行/s | 单条改 WAL 约 55~65× |
| 批量 1,000 + WAL + OFF | WAL / OFF | 1000 | 是 | 6.23 µs | 160,419 行/s | 与 DELETE 批量同量级 |
| 批量 100 + DELETE + OFF | DELETE / OFF | 100 | 是 | 9.83 µs | 101,775 行/s | 每帧小批量(≤150 行)参考 |
| 批量 100 + WAL + OFF | WAL / OFF | 100 | 是 | **5.53 µs** | 180,825 行/s | 小批量时 WAL 有 **1.8×** 价值 |
| **journal 放内存** + autocommit | MEMORY / OFF | 0 | 是 | **38.72 µs** | 25,825 行/s | 无 journal 文件 → 比 DELETE 快 28× |
| **journal 关闭** + autocommit | OFF / OFF | 0 | 是 | **36.45 µs** | 27,436 行/s | 同上(不推荐:崩溃可能损坏库) |
| 批量 1,000 + AUTOINCREMENT | DELETE / OFF | 1000 | 是 | 4.56 µs | 219,195 行/s | 与 4.90 差异在噪声内(≤10%) |
| 批量 1,000 + 窄表 19 列 / 23 列 | DELETE / OFF | 1000 | 是 | **1.55 / 1.73 µs** | 646,272 / 579,384 行/s | 列数/文本体积决定批量下限 |
**成本分解(三条独立证据)**:
1. **journal 文件 I/O ≈1.08 ms/行,占单条成本 96%~97%**:同一事务路径下,journal 从 DELETE 换成 MEMORY/OFF 后每行从 1120 µs 降到 ~37 µs,换 WAL+OFF 降到 17.2 µs ⇒ "journal 文件的创建/写入/删除"约 1.08 ms/行;
2. **原生文件操作基准**:空文件 `CreateFile+CloseHandle+DeleteFile` ≈0.15~0.18 ms/次、写 4 KB 后删除 ≈0.29~0.31 ms/次;带 `FlushFileBuffers` 在机械盘上 ≈**30.9 ms/次**(这解释了为什么 `synchronous=NORMAL` 在机械盘上表现极差);
3. **CPU 侧不是主因**:单条路径语句复用无改善;批量路径里"每行重 prepare"比复用多 7.2~10.6 µs/行,是批量模式下的次要优化项。
**结论**:
- 单条路径的性能由**每行一个事务**决定,`synchronous=OFF`/语句复用都救不了;要降低单条成本只能换 journal 方式(WAL/MEMORY,均有限)或**批处理**;
- **批处理是最有效手段**:DELETE+OFF+批 1000 达 4.90 µs/行(~229×);配合"每帧小批量"场景可再加 WAL(1.8×);
- **不要用 `synchronous=NORMAL`**;调试用途可接受 `synchronous=OFF` 的崩溃丢尾数据,但 `journal=MEMORY/OFF` 崩溃可能损坏库,只可作为诊断对照,不建议生产采用;
- `AUTOINCREMENT`(当前建表默认带 `PRIMARY KEY AUTOINCREMENT`,`sqlite.hpp:554`)在本机测量中差异 ≤10%(噪声内),语义上本项目不需要(每次运行新库、只增不删),去掉是"无风险的小优化"而非主要收益项;
- 本机磁盘是 SATA 机械盘,部署环境应优先 SSD,并把记录目录加入 Defender 排除(可显著降低单条绝对成本,但不能替代程序改造)。
### 4.5 端到端 A/B 实测(`REC_OFF` vs `REC_ON` 双 DLL)
#### 4.5.1 首轮场景(输入点迹路径,未形成可靠航迹)
场景:相扫 TWS;每帧 150 点、100 波位/圈、6 圈(首圈预热);杂波为簇状(凝聚后约 7 点/帧);目标上报速度与几何速度不匹配,本轮未起批(`trust_max=0`)——**这一轮隔离出了"输入点迹记录(150 行/帧)"单项开销**。原始数据:`build/perf_bench_pipeline/run_pilot_off|run_pilot_on/`(`frames.csv`/`circles.csv`/`summary.txt`/记录库)与汇总 `pilot_result.json`,可用 `rerun_pilot.ps1` 复跑(`--vr 60`)。
| 指标(统计第 1~5 圈,普通帧 n=495) | `REC_OFF` | `REC_ON` | 增量 | 倍数 |
|---|---|---|---|---|
| `data_preprocess` 每帧 mean | 0.0479 ms | **143.77 ms** | +143.72 ms | ~3000× |
| `data_preprocess` 每帧 p99 | 0.1297 ms | 462.08 ms | — | — |
| `track_process` 普通帧 mean | 0.1600 ms | 6.38 ms | +6.22 ms | ~40× |
| 每帧合计 mean | 0.2080 ms | **150.14 ms** | +149.93 ms | ~722× |
| 每帧合计 p99 | 0.4631 ms | 467.16 ms | — | — |
| 每圈合计 mean(100 帧) | 139.91 ms | **16,765.4 ms(16.8 s)** | +16.6 s | ~120× |
| 记录/圈 | 0 | 约 17,229 行 | — | — |
- 记录库:13,881,344 B;全运行(600 帧)行数:`RdpRecPoint` 90,000 + `RdpRecPointCohesionTWS` 4,169 + `RdpRecTempTrack` 9,205 = **103,374 行**(不含 `sqlite_sequence` 系统表)。全运行 ON/OFF 增量 = 98.30 s − 0.74 s ≈ **97.56 s**(含预热圈与判圈帧)→ **≈0.94 ms/行**;统计范围(第 1~5 圈,剔除预热圈)的增量为 83.1 s,与全运行口径一致;
- 每行成本随环境波动:本轮(机器空闲)≈0.94 ms/行;此前同场景在微基准并行压测时约 1.0 ms/行;微基准单条 mean 1.1~1.3 ms/行——量级一致,**结论不受影响**;
- **仅"每帧 150 行输入点迹"就产生约 144 ms/帧的额外耗时**(`data_preprocess` 增量 143.72 ms),单帧即超 10 ms 预算 14 倍;`track_process` 普通帧的 6.38 ms 来自每帧约 7 行凝聚点记录,按同一量级线性增长。
#### 4.5.2 含航迹场景(关联候选路径真实覆盖,最终 A/B 结果)
为覆盖 ⑥ 关联信息这条最大的路径,A/B 驱动修正了合成目标的速度一致性(几何径向速度 = 点迹上报径向速度,否则起批马氏距离被多普勒残差主导而无法起批),最终在 6 圈内形成 **186 条可靠航迹**,两配置的 `trust_num` 序列完全一致(124 → 180 → 186),**A/B 对照有效**。
**每帧耗时**(第 1~5 圈、普通帧 n=495,model==2):
| 指标 | `REC_OFF` mean | `REC_ON` mean | 增量 | 倍数 |
|---|---|---|---|---|
| `data_preprocess` | 0.0685 ms | **208.26 ms** | +208.19 ms | **3042×** |
| `track_process` | 0.2004 ms | 9.77 ms | +9.57 ms | 48.8× |
| 每帧合计 | 0.2689 ms | **218.03 ms** | +217.76 ms | **811×** |
- `REC_ON` 普通帧分布:**495/495 帧 > 10 ms**,489/495 帧 > 100 ms;最"好"的一帧 `data_preprocess` 仍要 89.5 ms(9× 帧预算)。
- `data_preprocess` 的 208 ms ≈ 150 行输入点迹 × **1.39 ms/行**,占普通帧 95.5%。
**判圈帧(model=1,每圈 1 帧,含关联/起始/消亡 + 记录)**:
| 指标 | `REC_OFF` | `REC_ON` |
|---|---|---|
| `track_process` mean | 318.07 ms | **59,218.01 ms(59.2 s)** |
| `track_process` p50 | 164.90 ms | 7,169.11 ms |
| `track_process` max | 751.04 ms | **170,269.92 ms(170.3 s,227×)** |
- **判圈帧尖峰的来源随圈变化**:第 4/5 圈(180/186 条航迹)的 **115.1 s / 170.3 s** 主要来自关联候选记录(`RdpRecAssoInfo` 逐圈 87,088 / 122,760 行);第 1~3 圈(0/0/124 条航迹)分别为 0.84 / 7.17 / 2.72 s,其中第 2 圈的 7.17 s **与关联无关**(该圈关联记录为 0 行),主要来自 ④ 临时航迹记录(约 2×10³~2.5×10³ 行,按 1.4 ms/行 约 3~3.5 s)叠加文件系统/杀软抖动。`REC_OFF` 同帧耗时分别为 2.4 / 50.1 / 164.9 / 621.9 / 751.0 ms(最大 751.0 ms,第 5 圈 186 航迹的算法计算)。
- **关联候选记录占判圈帧耗时约 99%**(第 4/5 圈 `RdpRecAssoInfo` 分别 87,088 / 122,760 行/圈;OFF 同帧算法本身 0.75 s)。
**每圈总耗时(100 帧合计)**:`REC_OFF` 均值 **344.79 ms** → `REC_ON` 均值 **81,050.64 ms(81.05 s)**,**235.1×**;未形成航迹的稳态圈 24.0~26.9 s/圈(超 1 s 圈预算 24~27×),形成航迹后 136.7 / 191.6 s/圈(137~192×)。整个 600 帧运行:`REC_OFF` 2.3 s / `REC_ON` 426.7 s(**185×**)。
**记录量**(`run_on/rdp_records/*.db`,35.32 MB):
| 记录表 | 行数 | 说明 |
|---|---|---|
| `RdpRecPoint` | 90,000 | 150 行/帧 × 600 帧 |
| `RdpRecPointCohesionTWS` | 4,170 | 约 7 行/帧 |
| `RdpRecAssoInfo` | **211,584** | 逐圈 1,736 / 87,088 / 122,760(随航迹数 124→180→186) |
| `RdpRecTempTrack` | 8,330 | 约 2,000~2,500 行/圈 |
| `RdpRecTrustTrack` | 304 | 可忽略 |
| **合计** | **314,388** | 平均 112.3 B/行;314,388 行 / 426.7 s ≈ **737 行/s(总量口径 1.36 ms/行)** |
> 三条独立口径互相印证"成本与行数严格线性":**总量口径 1.36 ms/行**(314,388 行 / 426.7 s)、**输入点迹口径 1.39 ms/行**(`data_preprocess` 208.26 ms / 150 行)、**凝聚点口径 1.40 ms/行**(`track_process` 9.77 ms / ~7 行)。A/B 的每行成本比微基准新库/单表的 1.1~1.3 ms/行略高,原因是 A/B 库增长到 35 MB、5 张表、页缓存压力更大且伴随 Defender 扫描——同一数量级,结论一致。
> 说明:该合成场景(200 目标、150 点/帧、凝聚后约 7 点/帧)的关联量级是**保守下限**(真实 150 个独立点迹时按 1.39 ms/行外推约 270 万行/圈、约 1 小时/圈);A/B 的绝对毫秒数不能直接当作现场实时预算,**可迁移的是 ON/OFF 比值、每行成本、以及 `M×N` 行数规律**。
### 4.6 为什么 Release 优化无效、为什么 OFF 就没有开销
1. **成本在 I/O 与事务,不在 CPU 计算**:`/O2` 优化的是算法指令,无法消除每条记录的文件创建/删除、锁、页写入;§4.4 中"语句复用对单条无效"也证明了这一点。
2. **记录点代码在 ON 构建中是实打实编译进去的**:`RDP_RECORD_ENABLE` 只控制"是否参与编译/是否链接",一旦定义,每条记录同步执行;`NDEBUG` 不会移除它们。
3. **Release 处理本身更快,使固定 I/O 开销占比更大**:OFF 时处理只需零点几毫秒/帧,ON 时每条记录 1 ms,相对放大即为数百倍(与 §4.5 实测一致)。
4. **OFF 的零开销来自编译期移除**(`CMakeLists.txt:118-158`、`rdp_record.h:42-45`):不编译、不链接、宏为空——这是"关闭即无性能损失"的正确实现方式,但不能替代"Release 排障时开启记录也要实时"的需求;后者必须靠**运行期架构改造**解决。
---
## 5. 瓶颈二:记录行数与负载成 `M×N` 关系(比逐条事务更致命)
### 5.1 行数模型
符号:`B`=每圈波位数(本项目参数 100,实际由宿主决定);`p`=每帧输入点数(≤150);`η`=凝聚存活率(输出/输入,未实测,场景表取 0.9);`N`=一次关联批凝聚点数(≈(B−1)·p·η,因为 `point_recv` 整圈累积);`M`=可靠航迹数(≤500);`A`=被关联消费的点数。
| 记录点 | 每 TWS 圈行数 | 上限 | 备注 |
|---|---|---|---|
| ① 输入点迹 | `B·p` | 15,000 | 每帧逐点 |
| ② 凝聚点迹 | ≈`N` | 14,850 | 每圈 99 次之和 = 下一圈关联批点数 |
| ④ 临时航迹 | `ΣL ≈ (1~3)(N−A)` | 病态 ~4.5×10⁵ | **全量快照**:一条 10 点临时航迹累计被写 1+2+…+10=55 行 |
| ⑤ 可靠航迹 | ≤`M` | 500 | 含外推成功航迹 |
| ⑥ 关联信息 | **`M×N`(精确等于候选对数)** | 500×14,850 = **7.425×10⁶** | 失败候选 `:554` + 被消费候选 `:710/:722`,当前最近邻实现下恰好每对候选一行 |
### 5.2 典型场景每圈行数(B=100,η=0.9,A≈M)
| 记录点 | 稀疏(p=10,M=20) | 中等(p=50,M=100) | 稠密(p=150,M=300~500) |
|---|---|---|---|
| `N`(关联批点数) | ≈891 | ≈4,455 | ≈13,365(上限 14,850) |
| ① 输入点迹 | 1,000 | 5,000 | 15,000 |
| ② 凝聚点迹 | 891 | 4,455 | 13,365 |
| ④ 临时航迹 | 0.9~2.6×10³ | 4.4~13×10³ | 13~39×10³ |
| ⑤ 可靠航迹 | ≤20 | ≤100 | 300~500 |
| ⑥ 关联信息 | 17,820 | 445,500 | **5,346,000~6,682,500** |
| **合计** | **≈2.1×10⁴ 行/圈** | **≈4.6×10⁵ 行/圈** | **≈5.4×10⁶~6.7×10⁶ 行/圈** |
| ⑥ 占比 | ≈86% | ≈97% | ≈99% |
### 5.3 按实测单行成本折算的每圈耗时
以本机 **~1.2~1.3 ms/行(微基准 3 次运行 mean 中位,§4.2)** 折算;若按 A/B 总量口径(1.36~1.40 ms/行,§4.5.2)则下表数值再高约 10%~15%,结论数量级不变:
| 场景 | ⑥ 关联信息 | 全部记录(合计) | 与 1 s 圈周期相比 |
|---|---|---|---|
| 稀疏(2.1×10⁴ 行) | 21~23 s | **≈25~27 s/圈** | 25~27× |
| 中等(4.6×10⁵ 行) | 535~579 s | **≈9.2~10.0 min/圈** | 550~600× |
| 稠密(5.4×10⁶~6.7×10⁶ 行) | 107~145 min | **≈1.8~2.4 h/圈** | 6400~8700× |
- **实测对照**:首轮 A/B(未起批)16.8 s/圈 对应约 1.72×10⁴ 行/圈(只有点迹/凝聚/临时航迹),落在上表稀疏量级;含航迹 A/B 的第 5 圈(186 航迹)约 14.0×10⁴ 行/圈(⑥ 122,760 行)+ 191.6 s → **1.37 ms/行**,行数与耗时的线性关系、以及 `M×N` 随航迹数增长(1,736 → 87,088 → 122,760 行/圈)均与模型一致。
- **关键推论:即使把写库优化到零成本,稠密场景 0.5 GB/圈的数据量也无法以 1 Hz 落盘**(需持续 ~500 MB/s 写带宽,超过本机 SATA 机械盘甚至 SATA SSD 的持续写能力)。所以 **⑥ 必须裁剪,不能只做性能调优**。
### 5.4 数据量估算
| 表 | 实测每行 DB 体积 | 说明 |
|---|---|---|
| `RdpRecPoint` | 119.6 B | 22 列标量 |
| `RdpRecAssoInfo` | 96.5~100.8 B | 18 列标量 |
| `RdpRecTempTrack` | 315.8 B | X/P 文本列(实测平均文本 196.7 B) |
| `RdpRecTrustTrack` | **1,369.7 B** | 8 个数组文本列(实测平均文本 **1,145.1 B**) |
- 稠密场景 ⑥ 5.35×10⁶ 行 × ~100 B ≈ **535 MB/圈**;④ 3.9×10⁴ 行 × 316 B ≈ 12 MB/圈;⑤ 500 × 1.37 KB ≈ 0.7 MB/圈。
- 记录库无轮转/无上限(每次运行一个新文件),长时间运行会到 GB~TB 级。
### 5.5 TAS 通道
TAS 每帧执行 ①③⑤⑥,但 `point_recv_tas` 每帧清空、关联只针对 1 条航迹(`track_asso_tas.cpp:263-283`),每 TAS 帧记录 **≤451 行**(①≤150 + ③≤150 + ⑤≤1 + ⑥≤150,见 §3.2);即使相扫每 TWS 圈有 4 个 TAS 帧,也只约 1.8×10³ 行/圈,是稠密 TWS ⑥ 的约 1/3000。**TAS 通道批处理后即可接受,不是本次瓶颈重点。**
---
## 6. 瓶颈三:处理线程上的实体构造(数组转文本)
### 6.1 实测构造成本
`RDP_RECORD(...)` 的实参在**调用方线程、进入 `Write` 之前**求值(`rdp_record.h:39`);`RdpRecTrustTrack`/`RdpRecTempTrack` 的构造函数要把 `X/P/...` 数组经 `arrayToString()` 转成文本,而 `arrayToString`(`rdp_record_entity.h:466-483`)对**每个元素调用 `elementToString` 构造一个 `std::ostringstream`**(`:450-453`)。
| 构造项 | 实测(3 次运行中位数) |
|---|---|
| `RdpRecTrustTrack` 构造(8 个数组共 135 个 double) | **133.56 µs/行** |
| `RdpRecTempTrack` 构造(X[4]+P[4][4] 共 20 个 double) | **19.44 µs/行** |
| `RdpRecPoint` 构造(22 标量) | 0.010 µs/行 |
| `RdpRecAssoInfo` 构造(18 标量) | 0.020 µs/行 |
| `arrayToString(double[6])` | 5.85 µs |
| `arrayToString(P[6][6])` | 35.73 µs(36 次转换,最贵) |
| `std::ostringstream` 格式化一个 double | 0.97 µs(其中 ostringstream 设施底价 ≈0.24 µs) |
| `std::to_string(double)` 格式化一个 double | 0.52 µs(约为前者 54%) |
| 手工 join `to_string(double[6])` | 3.17 µs(比 arrayToString 快 1.8×) |
其中 `RdpRecTrustTrack` 的 8 个数组:X 5.68 µs、P 35.73 µs、X1 5.93 µs、X2 5.94 µs、X3 5.81 µs、P2 36.02 µs、P3 34.36 µs、u 2.62 µs(P/P2/P3 三个 6×6 矩阵合计 ≈106 µs,占构造的 ~80%)。
**批量后构造成为主要成本**:端到端(构造 + 批 1000 写库)每行成本 TrustTrack ≈142.3 µs(构造占 94%)、TempTrack ≈23.9 µs(构造占 81%)、Point/Asso ≈3.8~5.9 µs(构造占 0.3%)。**这就是"为什么信任航迹/临时航迹不能只靠批处理"的实测依据**。
### 6.2 折算到每圈
| 表 | 行数(中等/稠密) | 构造耗时(中等/稠密) |
|---|---|---|
| ④ 临时航迹 | 4.4~13×10³ / 13~39×10³ | **86~253 ms / 253~758 ms 每圈** |
| ⑤ 可靠航迹 | 100 / 500 | **13.4 / 66.8 ms 每圈** |
- 注意:④⑤ 都发生在 **model=1 的同一帧**里,因此都会形成"单帧尖峰"。
- **批处理省不掉这部分**:批处理只把"每行一个事务"变成"每批一个事务",实体构造仍逐行在雷达线程完成(`hotpath_findings.md` §4.2)。要消掉它只能:
- 把构造/序列化搬到后台写线程(处理线程只拷贝源结构体快照);或
- 改列设计(数组存 blob、或元素展开表),不生成文本;或
- 减少记录行数(只记变化/末点/采样)。
---
## 7. 次要问题与放大因素
### 7.1 线程模型与互斥锁
- 接口线程模型未定义(`README.md:1020`,BUG-03:宿主应单线程调用),记录模块用**单连接 + 一把全局 `std::mutex`**(`rdp_record.cpp:41,183`)。单线程宿主的直接后果是:**磁盘事务的等待时间 100% 叠加到雷达处理线程**。
- 若宿主实际多线程调用(如收数据线程与处理线程并发),锁竞争会让记录开销进一步放大并造成优先级反转;`SQLITE_THREADSAFE=1` 还会在 SQLite 内部再加一层互斥。这一风险需现场确认宿主调用方式。
### 7.2 `AUTOINCREMENT`(语义上可去掉,收益不大)
- ormpp 的建表 SQL 对注册的自增主键**无条件追加 `PRIMARY KEY AUTOINCREMENT`**(`sqlite.hpp:552-560`,第 554 行),因此每行 INSERT 额外读+写 `sqlite_sequence` 表(多一个脏页 + 一条 journal 记录)。
- 本项目每次运行新建 db、只增不删,`INTEGER PRIMARY KEY` 的 rowid 本身就是单调的,**不需要 AUTOINCREMENT**。
- 实测:批量 1000 时带/不带 AUTOINCREMENT 的差异在测量噪声内(4.90 vs 4.56 µs/行,方向随运行翻转,≤10%),**属"无风险的小优化",不是主要收益项**;单条路径下该额外页会被 1 ms 级事务成本掩盖。
- 由于 ormpp 的 DDL 硬编码,去掉它需要记录模块自行预建表(`CREATE TABLE IF NOT EXISTS` 变 no-op,但 auto-key 映射仍会登记、insert 仍跳过 id 列)。
### 7.3 `journal_mode` 与 `synchronous=OFF` 的常见误解
- 当前只设置了 `synchronous=OFF`,未设 `journal_mode` → 默认 `DELETE`(提交时删除 journal 文件)。**这是单条路径最大的成本来源(实测占 96%~97%)**。
- `synchronous=OFF` 只省 `FlushFileBuffers`,**不省 journal 创建/写入/删除和锁**——所以"已经关了同步,为什么还慢"的疑问不成立:慢的是事务文件操作,不是 fsync。
- 设置 `PRAGMA journal_mode=WAL` 不能走 ormpp 的 `execute()`(它要求 `sqlite3_step` 返回 `SQLITE_DONE`,而该 PRAGMA 会返回一行,见 `sqlite.hpp:434-451`);可用同连接的 `query_s<std::tuple<int>>` 消费返回行,或 connect 前用 C API 设置。注意 WAL 默认 sync 仍是 FULL 语义,要同时设 `synchronous=NORMAL/OFF`;且本机机械盘上 `WAL+NORMAL` 因 fsync(≈31 ms/次)出现 checkpoint 抖动(§4.4)。
- 若引入批处理,`DELETE + synchronous=OFF` 已可达到 3.8~8.8 µs/行(批 1000),**WAL 不再是必需项**;只对"每帧 ≤150 行的小批量"再叠加 WAL 可再快 1.8×(§4.4 S11/S12)。
### 7.4 缓存、busy_timeout 与失败降级
- `cache_size` 默认仅 2 MB(`SQLITE_DEFAULT_CACHE_SIZE=-2000`);库变大、B-tree 变深后每行触页更多,可设 `PRAGMA cache_size=-8192`(8 MB)等(不返回行,可走 `execute()`)。
- `busy_timeout` 默认 0,Windows 上杀软/文件系统瞬时锁冲突会直接失败;当前无重试。
- **失败熔断缺失**:`rdp_record.cpp:189-195` 只在首次失败提示一次,但之后**每条记录仍然完整走"拼 SQL → prepare → step 失败"流程**;若库/磁盘异常,会持续消耗处理线程。建议失败后按实体或全局熔断。
### 7.5 环境放大因素(重要且常被忽略)
- 本机 `rdp_records` 位于 **SATA 机械盘**,且 **Defender 实时防护开启**;"每条记录新建+删除一个 journal 文件"的链条被文件系统元数据(NTFS `$LogFile`)与杀软扫描显著放大——1 ms 级单行成本中有很大比例来自环境而非 SQLite 本身(原生文件 create+write4K+close+delete 就需 0.29~0.31 ms/次)。
- 换到 SSD + 对记录目录加 Defender 排除/其他白名单,单行成本会明显下降;但**不能替代程序侧的批处理/裁剪/异步改造**(按 §5,即使降到 50 µs/行也仍不满足高行数场景)。
- 网络盘/共享盘上 SQLite 的 journal/WAL 行为更差(WAL 甚至可能不支持),记录目录应强制本地盘。
### 7.6 数据库增长与运维
- 每次运行一个新 db、无大小上限、无轮转、无定期清理;稠密场景 0.5 GB/圈,长时间开启会耗尽磁盘。
- 无 `VACUUM`(只追加场景一般不需要),但长时间运行后文件碎片/自由页问题需关注;调试库可接受。
---
## 8. 优化措施
### 8.1 措施总览
| # | 措施 | 针对瓶颈 | 实测/预期收益 | 风险/代价 | 改动面 |
|---|---|---|---|---|---|
| **P0-a** | **关联信息裁剪/采样**(⑥ 只记被取中候选 + 近失候选采样/白名单) | 数据量 `M×N` | 行数降 1~3 个数量级(稠密 5.4×10⁶ → 10³~10⁴ 行/圈) | 分析信息减少(可配置开关保留全量模式) | 业务记录点(`#ifdef` 内) |
| **P0-b** | **批量事务**(①~⑤、TAS ⑥ 帧末/圈末收集后 `RDP_RECORD_BATCH`) | 逐条事务 | DB 侧单行 **~1.1 ms → 3.8~8.8 µs(约 95~336×,批 1000,按实体)**;端到端(含构造)Point/Asso ≈207~335×、TempTrack ≈52×、TrustTrack ≈10×(实测) | 崩溃丢尾批、内存缓存 | 业务记录点 + 少量成员变量 |
| **P0-c** | **异步写线程 + 有界队列**(处理线程只入队 POD 快照,后台序列化+批量写) | 实时性保证(隔离磁盘抖动) | 处理线程开销降到"拷贝+入队"(~0.1~0.3 µs/行),磁盘抖动不再传导 | 实现复杂度、退出 flush、溢出丢弃策略 | 记录模块框架 + 记录点写法 |
| **P1-a** | **构造成本迁移/削减**(信任航迹数组列改 blob;或后台线程做 `arrayToString`;`to_string`/`to_chars` 替代 ostringstream) | 实体构造 133.6 µs/行 | 处理线程 ④⑤ 构造开销可降为 ~0(后台)或 ~1.8×(to_string);批量后构造占 TrustTrack 端到端 **94%** | 查询需解析 blob;改动实体/查询工具 | 记录模块 + 实体定义 |
| **P1-b** | **临时航迹只记增量/末点**(不再每圈全量快照) | ④ 重复行 | 行数降 10~55× | 丢失历史轨迹(可按圈采样保留) | 业务记录点 |
| **P1-c** | **SQLite 配置**:去 AUTOINCREMENT、`cache_size`、`synchronous`/WAL 按环境、`busy_timeout`、失败熔断 | 常数项 | 每帧小批量再叠加 WAL 约 1.8×;单条 WAL 55~65×(仅兜底);AUTOINCREMENT ≤10%(噪声内) | 崩溃一致性取舍 | 记录模块 |
| **P2-a** | **记录策略**:按圈采样(每 N 圈记 1 圈)、按表/字段白名单、运行期开关 | 数据量 | 任意比例降载 | 分析样本变少 | 记录模块 + 可选接口 |
| **P2-b** | **落盘/轮转/运维**:按圈或大小轮转、上限、Defender 排除、本地 SSD | 环境/磁盘 | 环境相关,幅度大 | 部署约束 | 部署 |
| **P2-c** | **备选:二进制原始日志 + 离线入库** | 全部 | 运行时近零成本(顺序写文件),离线转 SQLite | 需要额外工具链/查询步骤 | 记录模块 + 工具 |
### 8.2 P0-a:关联信息裁剪(决定性,必须做)
当前 ⑥ 在双层循环(`track_asso.cpp:441-445`)里逐候选写库:未过门限当场写(`:554`),过门限候选进 `associated_info`,最近邻消费时再写(`:710/:722`)——**每对候选恰好 1 行,总数 = M×N**。建议改为"收集 + 裁剪 + 分块批写":
1. **只记录被最近邻取中的候选**(`min_d_flag=true`,每圈约 `min(M,N)` 行)——这正好是复现关联决策所需的最小集合;
2. **近失候选采样**:`d_min < K×门限` 的候选按 1/N 采样(N 可配置,默认如 100),或每个点迹只保留 `d_min` 最小的前 K 个候选;
3. **白名单**:只对指定 `circle_id`/`track_index`/`point_id` 区间记录全量候选(复现特定问题时开启);
4. **运行期开关**:`RDP_RECORD_ASSO_FULL=0/1`(环境变量或新增接口),默认关闭全量失败候选;
5. 收集到 `std::vector<RdpRecAssoInfo>` 后,**每 4,096 行或圈末 flush 一次**(限制内存尖峰)。
预期:⑥ 从 `M×N` 降到 `O(N + M)`(稀疏 ~900 行、中等 ~4.5×10³ 行、稠密 ~1.3×10⁴ 行/圈),⑥ 单表写库降到 **约 5~80 ms**(按批 1000 实测 3.8~5.9 µs/行),数据量从 535 MB/圈降到 ~1.3 MB/圈;但**整圈(①~⑥ 合计)仍有约 0.2~0.7 s**(稠密场景约 5.5~8.1 万行),需继续用异步写线程/按圈采样消化。
### 8.3 P0-b:批量事务(改动最小、收益最大)
框架**已具备**批量能力:`RDP_RECORD_BATCH(recs)`(`rdp_record.h:41`)→ `Write(const std::vector<T>&)`(`rdp_record.cpp:198-225`)→ ormpp 批量插入(一次 prepare + 一个 `BEGIN/COMMIT`,`sqlite.hpp:859-902`)。**当前业务侧 0 个调用点**,只需按"天然批次边界"改造记录点:
| 记录点 | 建议批次边界 | 做法(示意) |
|---|---|---|
| ① 输入点迹 | 每帧 `data_preprocess` 循环结束 | `std::vector<RdpRecPoint> recs;` 循环内 `emplace_back(...)`(`#ifdef RDP_RECORD_ENABLE` 包裹),循环后 `RDP_RECORD_BATCH(recs);` |
| ② TWS 凝聚点 | 每次 `dot_coh_process_buff` 输出循环末(或跨帧累积到圈末) | 同上,一行/点 |
| ③ TAS 凝聚点 | `dot_coh_tas_process` 输出循环末 | 同上 |
| ④ 临时航迹 | `track_init.cpp:110-117` 双层遍历收集后一次批写 | 先按 P1-b 裁剪,再批写 |
| ⑤ 可靠航迹 | `track_asso.cpp:71-165` / `track_asso_tas.cpp:44-125` 输出循环收集后批写 | 行数 ≤500,批写 + P1-a |
| ⑥ TWS 关联信息 | `model_filter` 内先收集,圈末/每 4096 行 flush | 必须先做 P0-a 裁剪 |
| ⑥ TAS 关联信息 | `model_filter` 点循环结束(`:365` 后)/候选清理(`:497-500`)| 失败候选也先收集,帧末一批写 |
注意:
- 关掉记录功能时这些代码必须整体在 `#ifdef RDP_RECORD_ENABLE` 内(保证交付构建零代码、零告警);
- 批写时**内存占用要有上限**(⑥ 尤其);批过大时失败会丢整批,可接受(调试用途)但需记录丢批计数;
- 批处理**不改变**记录点的语义(先记录再消费/后记录),只是把"逐行提交"变成"一批提交"。
### 8.4 P0-c:异步写线程(实时性的最终保证)
```
雷达处理线程 记录写线程(独立)
──────────── ─────────────────
RDP_RECORD(...) 循环:
├─ 构造紧凑 POD 快照(或直接拷贝源结构体) ├─ 从队列取出一批(如 ≤2000 行 / 20 ms)
├─ 有界队列 push(无锁 SPSC 或细粒度锁) ├─ 序列化(原 ostringstream 逻辑移到这里)
└─ 返回(~0.1~0.3 µs/行) ├─ 一个事务批量 insert(现有 Write(vector) 路径)
└─ 失败熔断/丢批计数
队列满策略:丢弃(丢新或丢旧)+ 原子计数;Shutdown 时停止接收并排空(有超时)
```
- **收益**:磁盘抖动/Defender/事务尾延迟与雷达线程彻底解耦;处理线程只有"拷贝+入队"的线性小开销,**可给出确定的实时上界**;
- **前提**:⑥ 必须先裁剪(否则 10⁶ 行/圈的入队本身也会造成尖峰与内存爆炸);
- **实现要点**:
- 队列容量与高水位监控(如 64K~256K 行/16~64 MB,按行类型分队列);
- 丢弃策略与计数(`dropped_rows`),运行结束打印,保证"丢数据可解释";
- `Destroy()`/`Shutdown()` 时先 flush 再关库(`rdp_record.cpp:154-166` 需配套改造);
- 记录实体构造(数组转文本)**移到写线程**,用源结构体快照(`PointRecv`/`Trust_Track`/`Asso_Info`/`Temp_track` 拷贝)作为队列元素;注意 `Trust_Track` 含 `std::vector` 成员(`Hight_smooth`),拷贝成本略高,可只拷贝需要的字段。
- **建议只对"行数随负载线性增长"的 ①~⑤ 走异步;⑥ 裁剪后也可走异步**。
### 8.5 P1-a:构造成本的处理
- **方案 A(推荐)**:把 `arrayToString` 全部移到异步写线程(处理线程只入队源结构体),处理线程构造成本 → 0;写线程每行 133.6 µs(TrustTrack)可被后台线程吸收(500 行/圈 ≈ 67 ms,远小于 1 s 圈预算)。
- **方案 B**:改实体列设计——`X/P/...` 存 **blob**(如 `std::vector<double>`/定长二进制),避免文本化;查询时用 Python/工具解析。单行 1.37 KB → ~0.3 KB,同时省掉 ostringstream 与 SQLite 文本绑定。
- **方案 C(最小改动)**:`arrayToString` 用 `std::to_string`/`std::to_chars` + 预留 `reserve` 手工拼接替代 `ostringstream`(实测 1.8×:0.52 vs 0.97 µs/double),仍保留文本列。
- 附加:只记录真正需要的列(当前 ⑤ 未记 `P1`、`Quality` 等,勿盲目补列);④ 只记增量/末点/采样。
### 8.6 P1-b:SQLite 配置与健壮性
| 项 | 建议 | 依据/注意 |
|---|---|---|
| 批事务 | 必做(P0-b) | 实测 DB 侧约 95~336×(批 1000,按实体) |
| 语句复用 | 批处理路径已由 ormpp 复用;若自建写入层则按实体缓存 `sqlite3_stmt` | 批量内每行重 prepare 多花 **7.2~10.6 µs/行**(13.46→4.90) |
| `AUTOINCREMENT` | 预建表去掉(改普通 `INTEGER PRIMARY KEY`) | 差异在噪声内(≤10%),语义上无必要;每行少一次 `sqlite_sequence` 读写 |
| `journal_mode` | 有批处理时 `DELETE + synchronous=OFF` 已足够;每帧小批量(≤150 行)可加 `WAL + synchronous=OFF` 再快约 1.8×;单条兜底用 WAL(55~65×) | 设置方式见 §7.3;WAL 需本地盘;注意 `-wal/-shm` 与 checkpoint;**不要 `synchronous=NORMAL`** |
| `cache_size` | 视库大小设 8~32 MB(不返回行,可 `execute()`) | 减少 B-tree 页进出 |
| `busy_timeout` | 设 1~5 s + 有限重试 | 默认 0 会直接失败 |
| 失败熔断 | 插入失败后按实体/全局熔断,避免每条都走失败路径 | 当前 `rdp_record.cpp:189-195` 无熔断 |
| 同步策略 | 调试库接受 `synchronous=OFF` 的崩溃丢尾数据;如需更强一致性改 `NORMAL` 并接受性能下降(机械盘上可能差 8~12×) | 文档已有说明(`docs/data_record_debug.md:56`) |
### 8.7 P2:记录策略与运维
- **按圈采样**:`circle_id % N == 0` 时记录,N 可配置(如 1/5/10),负载与数据量按比例下降;分析时仍能覆盖多圈。
- **按表开关**:现场排障通常只需 1~2 张表(例如只看输入点迹 + 关联信息),其余表可关闭。
- **记录库轮转/上限**:按圈数或文件大小滚动(如每 100 圈一个新 db),并限制总目录大小;避免 GB~TB 级文件。
- **环境**:记录目录放**本地 SSD**、加入 Defender 排除(需合规审批);避免网络盘。
- **运行期开关**:增加"开始/停止记录"的接口或环境变量(不改变交付接口导出表的前提下谨慎设计),现场按需开启。
### 8.8 备选方案:二进制原始日志 + 离线入库
如果目标是"**彻底不影响实时性**"且可接受离线转换:
- 记录点只把定长 POD 快照顺序追加到二进制文件(`fwrite`/内存映射,写线程或缓冲),运行时开销 ~µs/行;
- 运行结束后用工具把二进制转成 SQLite(或直接供 Python/Pandas 读),分析体验与现在一致;
- 优点:实时性最好、无 SQLite 事务抖动;缺点:需要额外的转换工具与格式版本管理,查询前多一步。
> 对"Release 现场排障"这种低频、短期需求,方案 8.8 往往比"把 SQLite 在线写做快"更省事;对"长期在线记录"则推荐 8.2~8.4 的异步 + 裁剪 + 批处理。
### 8.9 不建议的做法
- ❌ 只把 `synchronous=OFF` 当作解决:它只省 fsync,实测单条仍 ~950~1120 µs;
- ❌ 只加 `cache_size`:对"每条一个事务"帮助有限;
- ❌ 只用 WAL 不批处理:单条 17~210 µs/行(`NORMAL` 更差),高行数场景仍超预算;
- ❌ 只做批处理不做 ⑥ 裁剪:中等场景仍约 2.5 s/圈、稠密约 30~40 s/圈(见 §4.3/§5.3),仍不满足 1 s 圈预算;
- ❌ 用"交付构建保持 OFF"回避问题:那是交付策略,不是 Release 排障场景的解决方案;
- ❌ 把记录点当日志用、在热循环里无条件记录(当前 ⑥ 即此模式)。
---
## 9. 推荐实施路线与验收标准
### 9.1 分阶段实施
| 阶段 | 内容 | 预期效果 | 风险 |
|---|---|---|---|
| **阶段 0(立即,不改代码)** | 只在必须排障时开启 `Release+ON`;优先用 Debug+ON 或短时采数;记录目录放 SSD、加 Defender 排除 | 缓解现场影响;不能解决实时性 | 低 |
| **阶段 1(P0-b 批处理)** | ①~⑤、TAS ⑥ 改帧末/圈末 `RDP_RECORD_BATCH`;⑥ 先分块收集 | ①~⑤ DB 写入从 ~1.1 ms/行降到 3.8~8.8 µs/行(实测约 95~336×,按实体);输入点迹每帧 150 行 → 约 0.6~2.1 ms(取决于批大小)。注意 ④⑤ 端到端仅 ~10~52×(构造仍是瓶颈,需阶段 3) | 低(接口已存在) |
| **阶段 2(P0-a 裁剪 + P1-b)** | ⑥ 只记取中/近失采样/白名单;④ 只记增量;去 AUTOINCREMENT、cache_size、失败熔断 | 稠密场景 ⑥ 从 5.4×10⁶ 行/圈降到 10³~10⁴ 行/圈(该表单表从 ~100 min 降到约 5~80 ms);整圈(①~⑥)仍约 0.2~0.7 s,需阶段 3 才能稳定实时 | 中(分析信息取舍需确认) |
| **阶段 3(P0-c 异步 + P1-a 构造迁移)** | 有界队列 + 写线程 + 后台序列化/批写;Shutdown flush;丢包计数 | 处理线程新增 p99 有确定上界(目标 ≤1 ms/帧,判圈帧 ≤5 ms);**§9.2 的 p99≤1 ms 验收目标需要阶段 2+3 共同达成,仅阶段 1 批处理时仍可能出现 ms 级尖峰** | 中高(框架改动,需要长跑验证) |
| **阶段 4(P2 运维)** | 按圈采样、记录库轮转、Defender 排除、监控指标 | 长时间运行可控 | 低 |
### 9.2 验收指标(建议)
| 指标 | 目标 |
|---|---|
| 处理线程每帧新增耗时(记录功能带来) p99 | **≤1 ms**(10 ms 帧周期的 10%) |
| 判圈帧(model=1)新增耗时 p99 | **≤5 ms**(不使该帧超出预算;需要时把 ④⑤⑥ 写线程化后进一步降低) |
| 连续运行 1 小时实时性 | 无因记录导致的帧超时/积压(用宿主现有节拍监控判定) |
| 记录完整性 | 行数与业务量模型一致;`pragma integrity_check = ok`;丢包计数可解释(丢弃率 0 或明确阈值内) |
| 记录库增长 | 按采样/轮转策略可控(如 ≤ X MB/圈、总目录上限) |
| 交付构建 | `RDP_DATA_RECORD=OFF` 时 DLL 导出表/依赖/体积与改动前一致,记录代码零参与(沿用现有 CI 校验) |
### 9.3 验证与复测方法
1. **处理线程打点**:在记录点前后嵌 `QueryPerformanceCounter`(仅 `RDP_RECORD_ENABLE` 构建),输出每帧/每圈直方图(mean/p50/p99/max),对比 ON/OFF;
2. **数据库侧校验**:用 SQL 统计每圈行数(`select circle_id,count(*) from RdpRecAssoInfo group by circle_id;` 等),与业务量模型对齐;`pragma integrity_check`;
3. **队列/丢弃监控**:异步方案输出队列高水位、丢弃行数、写线程积压时间;
4. **长时间运行**:1 h 连续跑,记录帧时延 p99 随时间变化与 DB 增长;
5. **现场复测**:把本报告 A/B 驱动替换为宿主真实数据(或短时抓包回放),在目标机器上重复 §4.5 的 ON/OFF 对比,校准 `M`、`N`、每行成本三个关键参数。
---
## 10. 风险与注意事项
| 风险 | 说明/缓解 |
|---|---|
| 异步丢数据 | 队列满丢弃/退出时未 flush 会丢记录;用计数与日志显式化,调试用途可接受,但必须可解释 |
| `synchronous=OFF`/WAL 的崩溃一致性 | 进程异常/断电可能丢尾部数据或损坏库;调试库可接受,若需更强一致性改 `NORMAL` 并接受性能下降 |
| 裁剪 ⑥ 导致分析信息减少 | 保留"全量候选"运行期开关,需要时短时开启;默认只记关键候选 |
| WAL 文件与 checkpoint 抖动 | `-wal` 增长;本机机械盘上 `WAL+NORMAL` 批量均值 53.7 µs/行(比 WAL+`sync=OFF` 差 8.6×、比 DELETE 批量差约 11×)且 checkpoint 周期出现 10~100 ms 级尖峰;优先 `DELETE + 批处理` 或 `WAL + sync=OFF` + SSD |
| 多线程宿主 | 若宿主实际多线程调用,单连接+全局锁会串行化并放大延迟;异步写线程方案需明确单写者 |
| 记录库无限增长 | 必须加轮转/上限,避免磁盘耗尽 |
| 记录功能与交付隔离 | 所有改动限制在 `#ifdef RDP_RECORD_ENABLE` 与记录模块内部;交付构建(OFF)必须保持零代码、零导出、零依赖 |
| 合成场景代表性 | 本报告 A/B 为压力场景,绝对毫秒数与真实现场不同;上线前需用真实宿主数据复测 |
---
## 11. 优化实施与验收结果(2026-10-20)
依据 `requirements.md` 的决策实施(P0-b 批量事务、P0-c 异步写线程+有界队列、P1-a 方案A 构造迁移、P1-b SQLite 配置与健壮性采用;P0-a 关联裁剪、P2 记录策略不采用)。
> 编号映射说明:`requirements.md` 中的 **P1-b = 本报告 §8.6(SQLite 配置与健壮性)**;§8.1 总览表中旧编号"P1-b = 临时航迹只记增量"**未纳入本次需求**(未实施)。
### 11.1 实现摘要
| 子系统 | 实现 |
|---|---|
| 记录框架 | 处理线程只做"收集源结构体 → 一次入队";独立写线程做实体构造(`arrayToString` 全部移入写线程)+ 批量事务;有界队列(默认 64 MB / 4096 批),满则整批丢弃并计数(不阻塞处理线程);写线程 Windows `BELOW_NORMAL` 优先级;`Shutdown()` 停止入队→排空→`wal_checkpoint(TRUNCATE)`→关库→打印 `Stats`;支持 Destroy→GetB 重连(新库;同秒文件名冲突追加 `_1` 后缀) |
| 业务记录点 | 12 处(6 个 cpp)改为按帧/圈收集后调用 `RDP_RECORD_POINTS`/`RDP_RECORD_COHESION_TWS`/`RDP_RECORD_COHESION_TAS`/`RDP_RECORD_TEMP_TRACKS`/`RDP_RECORD_TRUST_TRACKS`/`RDP_RECORD_ASSO_INFOS`;关联信息按 `RDP_RECORD_ASSO_CHUNK_ROWS`(131072)分块入队;全部位于 `#ifdef RDP_RECORD_ENABLE` 内,`OFF` 构建零代码 |
| SQLite | `journal_mode=WAL`(不支持时回退)、`synchronous=OFF`、`cache_size=-8192KB`、`busy_timeout=2000ms`;建表后去 `AUTOINCREMENT`(读取 `sqlite_master` DDL 后重建空表);每表建表/插入失败只提示一次,连续错误 ≥10 次全局熔断 |
| 参数 | `parameters.h` 新增 10 个可调宏(队列容量/批数、写批行数/等待、关联分块、缓存、busy_timeout、WAL 开关、熔断阈值、丢弃日志间隔),均带 `#ifndef` 可覆盖 |
| 遗留接口 | `RDP_RECORD`/`RDP_RECORD_BATCH`(实体同步写)保留,文档标注仅测试/特殊场景使用 |
### 11.2 验收结果
| # | 项目 | 结果 |
|---|---|---|
| A1 | 构建矩阵(PS/MS × Release/Debug × REC_ON/OFF,8 配置) | **PASS**:全部 0 warning 0 error |
| A2 | `OFF` 等价性(对比改动前 HEAD 构建的基线 DLL) | **PASS**:体积 182,784 B 相同;34 个导出符号完全一致;9 个依赖 DLL 完全一致 |
| A3 | 记录内容(dup/6 圈/186 航迹场景) | **PASS**:`RdpRecPoint` 90,000、`RdpRecPointCohesionTWS` 4,170、`RdpRecAssoInfo` 211,584、`RdpRecTempTrack` 8,330、`RdpRecTrustTrack` 304,合计 **314,388 行**,与改动前完全一致;`trust_num` 序列一致 |
| A4 | 数据库正确性 | **PASS**:`integrity_check=ok`;DDL 不含 `AUTOINCREMENT`;`journal_mode=wal`;各表 `id` 严格递增;`OFF`/`ON` 均无 `-wal/-shm` 残留(Shutdown 检查点后关闭) |
| A5a | 处理线程普通帧新增耗时(`REC_ON−REC_OFF`) | **PASS**:多次运行 p99 **0.08~0.17 ms**(阈值 ≤1 ms),p50 ≈ 0.02 ms |
| A5b | 处理线程判圈帧新增耗时(压测场景,186 航迹/单圈约 12.3 万关联候选) | **未达 5 ms 目标**:多次运行 p99 **12.8~24.0 ms**、p50 2.9~4.1 ms、mean 5.9~8.7 ms、max 24.5 ms(原因:P0-a 未采用,单圈需拷贝/记录约 12.3 万行关联候选;详见 §11.3) |
| A5c | 受控负载参考(50 目标 / 50 点每帧,49 航迹) | 判圈帧新增:中位 **3.07 ms**、最大 **5.96 ms**(基本达到 5 ms 量级,随负载线性变化) |
| A6 | 队列/长跑(60 圈压测,约 43.5 s) | **PASS**:9,987,370 行、`dropped_rows=0`、`write_errors=0`、队列峰值 19.8 MB(<64 MB)、DB 1.06 GB |
| A7a | 故障注入:`rdp_records` 为普通文件(目录不可建) | **PASS**:`Init()` 静默失败,驱动 exit=0、无崩溃、不产生记录库 |
| A7b | 故障注入:小队列构建(cap=1 KB) | **PASS**:`dropped_rows=314,388` 有计数、处理流程正常结束 |
| A7c | 重连(GetB→Destroy→GetB) | **PASS**:生成 2 个独立 db 文件(同秒冲突追加 `_1`),两轮均写入成功 |
| — | 端到端墙钟(600 帧) | `REC_OFF` 1.8 s / `REC_ON` 2.1 s(改动前 `REC_ON` 为 **426.7 s**) |
> 验收脚本与原始日志:`build/analysis/verify/run_acceptance.ps1`、`latency_eval.py`、`check_record_db.py`、`failure_injection.ps1`、`acceptance_final.log`、`latency_eval.log`、`check_record_db.log`。
### 11.3 遗留问题与决策点
1. **判圈帧压测开销仍高于原定 5 ms 目标(多次运行 p99 12.8~24.0 ms、中位 2.9~4.1 ms、均值 5.9~8.7 ms)**:这与 `requirements.md` 不采用 P0-a 的决策直接相关——压测场景单圈 `M×N≈12.3 万` 行关联候选,仅"收集+入队"就需在判圈帧复制十几 MB 数据;对照的受控负载(49 航迹)中位仅 3.1 ms、最大 6.0 ms。是否满足现场要求取决于实际 `M`/`N`;若现场也接近压测量级且要求判圈帧 ≤5 ms,需要重新评估 P0-a(关联候选裁剪/采样)或按圈采样。
2. **实时性总体结论**:普通帧几乎无感(p99 +0.11 ms);压测判圈帧 `REC_ON` 绝对耗时约 0.62 s(`REC_OFF` 同帧约 0.59 s),仍小于 1 s 圈周期,但相对开销(+24 ms)来自未裁剪的行数。建议现场用真实数据按 §9.3 复测。
3. **异步语义变化**:进程崩溃/未调用 `Destroy()` 时,队列与 WAL 尾部记录可能丢失;队列满时整批丢弃,均由 `Stats` 计数(`dropped_rows`)。调试用途可接受,但分析前应确认 `dropped_rows=0`。
4. **跨表写入顺序**不再保证(各表内顺序保持);依赖 `id` 跨表排序的分析脚本需改用业务时间/`circle_id` 等字段关联。
5. **版本号未提升**(仍为 V1.5.8),变更记录于 `CHANGELOG.md [Unreleased]`;正式发布时再归档。
---
## 附录 A:关键代码位置索引
**A.1 分析时(优化前)关键位置**
| 主题 | 位置 |
|---|---|
| 记录宏与开关 | `rdp_record.h:37-54`;`CMakeLists.txt:25-57,118-158` |
| 单条写入实现 | `rdp_record.cpp:174-196`;`third_party/ormpp/ormpp/sqlite.hpp:834-856` |
| 批量写入实现 | `rdp_record.cpp:198-225`;`sqlite.hpp:859-902`(`transaction_` 默认 true 在 `:926`) |
| SQL 字符串生成 | `third_party/ormpp/ormpp/utility.hpp:438-521` |
| 建表/AUTOINCREMENT | `third_party/ormpp/ormpp/sqlite.hpp:489-585`(`:554` 追加 `PRIMARY KEY AUTOINCREMENT`) |
| `synchronous=OFF` | `rdp_record.cpp:144-147` |
| 记录点 ① | `data_process.cpp:60,132` |
| 记录点 ② | `dot_coh.cpp:230` |
| 记录点 ③ | `dot_coh_tas.cpp:73` |
| 记录点 ④ | `track_init.cpp:110-117` |
| 记录点 ⑤ | `track_asso.cpp:163`;`track_asso_tas.cpp:122` |
| 记录点 ⑥ | `track_asso.cpp:554,710,722`;`track_asso_tas.cpp:363,499` |
| 数组转文本 | `rdp_record_entity.h:448-483`(`arrayToString`) |
| 实体定义与构造 | `rdp_record_entity.h:52-443`;`struct.h:40-170` |
| TWS 关联批累积 | `dot_coh.cpp:227-231`;`track_asso.cpp:29-68`;`track_init.cpp:60,108` |
| 实时性/线程模型已知问题 | `README.md:1020`(BUG-03) |
| 功能说明与已知限制 | `docs/data_record_debug.md:15,47-59,197-204` |
**A.2 优化实施后(V1.5.8 优化,2026-10-20)关键位置**
| 主题 | 位置 |
|---|---|
| 公共 API / 宏 / `Stats` | `rdp_record.h:31-101`(`Stats`、`stats`、6 个异步入队接口、6 个记录宏;遗留 `RDP_RECORD` 同步宏保留) |
| 队列与入队/丢弃 | `rdp_record.cpp:472-546`(`EnqueueBatch`:字节/批数上限、整批丢弃、峰值统计) |
| 写线程主循环 | `rdp_record.cpp:548-637`(drain、凑批、分块提交、异常兜底、Windows `BELOW_NORMAL` 优先级) |
| 批量事务/实体构造 | `rdp_record.cpp:367-455`(`InsertEntities`/`FlushKind`:实体在写线程构造、每批一个事务) |
| SQLite 初始化 | `rdp_record.cpp:247-344`(WAL/sync/cache/busy_timeout;`CreateTable` 去 AUTOINCREMENT);`parameters.h:50-92` 可调宏 |
| 生命周期/统计/重连 | `rdp_record.cpp:661-820`(`Init` 启动写线程、`Shutdown` 排空+检查点+打印 stats、同秒文件名后缀) |
| 业务记录点(批量入队) | `data_process.cpp:28-31,66-70,111-114,155-160`;`dot_coh.cpp:226-240`;`dot_coh_tas.cpp:69-83`;`track_init.cpp:108-118`;`track_asso.cpp:69-74,170-176,447-460,570-585,733-765,855-865`;`track_asso_tas.cpp:42-47,128-136,263-276,370-390,510-580` |
| 验收脚本/证据 | `build/analysis/verify/`(`run_acceptance.ps1`、`latency_eval.py`、`check_record_db.py`、`failure_injection.ps1`);`build/analysis/optim_baseline.md`、`build/analysis/exports_*.txt`、`build/analysis/build_matrix_final.log` |
## 附录 B:实测数据明细
### B.1 微基准(`build/analysis/bench_micro.md` + `build/perf_bench_micro/`)
- `build/analysis/bench_micro.md`:微基准报告(单条/批量/构造/SQLite 配置、3~5 次运行中位数与 min~max);
- `build/perf_bench_micro/bench_record.cpp`:单条/批量/构造基准(链接现有 `rdp_record.lib`);
- `build/perf_bench_micro/bench_sqlite.cpp`:16 种 SQLite 配置 + 原生文件操作基准(链接现有 `sqlite3.c.obj`);
- `results/`(含 `run1..run5`):逐行/逐批原始 CSV、环境信息、SQLite 编译选项、写后只读校验。
### B.2 端到端 A/B(`build/perf_bench_pipeline/`)
- `driver.cpp`:只使用公开 DLL 接口的合成数据驱动(TWS,100 波位/圈、150 点/帧、200 目标、相扫判圈);
- `run_off/`、`run_on/`:两套 DLL 的运行目录(各含 `frames.csv`、`circles.csv`、`summary.txt`);
- `run_on/rdp_records/*.db`:记录库(可对比行数/体积);
- `build/analysis/bench_pipeline.md`:A/B 报告。
### B.3 关键换算
- 首轮 A/B(未起批,`run_pilot_off|run_pilot_on`):全运行 103,374 行 → ≈97.6 s 增量 → **≈0.94 ms/行**(环境空闲轮次;并行压测时约 1.0 ms/行);
- 最终 A/B(186 航迹):314,388 行 → 426.7 s → **≈1.39 ms/行**(35.32 MB 库);
- 首轮 A/B 每圈记录数:`RdpRecPoint` 15,000/圈 + 凝聚 ~695/圈 + 临时航迹 ~1,534/圈 → 约 1.72×10⁴ 行/圈 → 16.8 s/圈;
- 微基准(3 次运行中位数):单条 mean 1.22~1.33 ms/行、p50 0.73~0.78 ms;批量 1000 = 3.8~8.8 µs/行;
- 微基准构造:`RdpRecTrustTrack` 133.56 µs/行、`RdpRecTempTrack` 19.44 µs/行。
## 附录 C:术语
| 术语 | 说明 |
|---|---|
| 帧/波位 | 一次 `data_preprocess` 调用;TWS 一圈含 `B` 个波位 |
| model | `data_preprocess` 返回值:1=TWS 满圈、2=TWS 蓄数据、3=TAS、0=无效 |
| `N` | 一次 TWS 关联批的凝聚点数(整圈累积) |
| `M` | 可靠航迹数(`trust_track.size()`,≤500) |
| 隐式事务 | 单条 SQL 在 SQLite autocommit 模式下自动开启/提交的事务(每条记录一次 journal 生命周期) |
| 批处理 | 把多行收集到一个 `vector` 后用一次 `BEGIN/COMMIT` 写入 |
| 异步写线程 | 处理线程只入队,独立线程负责序列化与批量入库 |
| 近失候选 | 未通过关联门限但 `d_min` 接近门限的候选(用于分析门限灵敏度) |
+110 -75
View File
@@ -45,16 +45,18 @@ flowchart LR
调用点(`data_process.cpp`):
```cpp
// L130-161,仅 model==1(TWS 满一圈)时执行
// track_process()(data_process.cpp L167 起),仅 model==1(TWS 满一圈)时执行
track_asso.track_asso_process(&point_recv, &trust_track, ...); // 入口删除 Use_Flag==1 的点
track_die.track_die_process(&trust_track, ...);
track_init.track_init_process_logic(&point_recv, &trust_track, &temp_track,
Trust_Track_Output, Trust_track_num_Output, Work_Parameter);
Trust_Track_Output, Trust_track_num_Output);
```
> 工作参数为**全局对象 `rdp_parameter`**(`struct.h` 中 `extern` 声明、`data_process.cpp` 定义并零初始化),由公开接口 `parameters_reload(配置文件路径)` 从 TOML 配置文件加载;各模块直接以 `rdp_parameter.<field>` 访问,不再作为函数形参逐层传递(参数来源见 README 第 10 节;改造前为宿主下发 `RadarPara` + `parameters.h` 宏,更早则由 `const RDP_parameter&` 形参传入)。
### 1.2 调用频率 = 扫描圈
`data_preprocess` 在 `Data_Input[0].Beam_index_aiz == 0` 时返回 1(`data_process.cpp` L51),宿主据此调用 `track_process(model=1)`。
`data_preprocess` 判为一圈完成时返回 1(`data_process.cpp`),宿主据此调用 `track_process(model=1)`。判据由配置项 `[radar_params] scan_mode` **运行时**选择(0 相扫 / 1 机扫;替代原编译期 `PHASE_SCANNING`/`MECHANICAL_SCANNING` 分支):**scan_mode=0** 比较前后两帧方位波位号(当前 ≤ 上一帧即满圈,`last_beam_num` 参与判断,清空后/首帧即满圈);**scan_mode=1** 判 `Data_Input[0].Beam_index_aiz == 0`(详见 README 7.2 与 `docs/data_process_flow_report.md` 2.2 节)。
因此 **`track_init_process_logic()` 每次调用对应 TWS 一圈**,`Temp_track::buff_round`(已缓存圈数)正是以「调用次数」为单位累加的。
### 1.3 类接口
@@ -69,7 +71,7 @@ track_init.track_init_process_logic(&point_recv, &trust_track, &temp_track,
| `tmp_track_die(...)` | private | 临时航迹消亡 |
| `alpha_cal_track_init(...)` | private | 三点折角(度) |
| `track_init_prohibit(...)` | private | 起批屏蔽区判断 |
| `track_index_mangement` | private | 航迹号(1~500)分配器 |
| `track_index_mangement` | private | 航迹号(1~`track_index_max`,默认 500)分配器 |
> `temp_track`(`std::vector<std::vector<Temp_track>>`)**不是** `Track_Init` 的成员,而是 `Data_Process::temp_track`,由调用方传入并跨圈保持;`track_clear_all()` 中清空(`data_process.cpp` L233),`Track_Init::reset()` 只清自己的 `point_process`。
@@ -90,6 +92,7 @@ track_init.track_init_process_logic(&point_recv, &trust_track, &temp_track,
| `asso_flag` | 0 未关联 / 1 已关联(待删除)/ 2 去重淘汰 | 临时状态标记 |
| `pitch_num`、`speed_dim[129]`、`range_dim[8]` | 俯仰波位、十字星数据 | 纯透传 |
| `temp_track_id` / `point_id` / `circle_id` | 临时航迹 ID / 末关联点迹 ID / 处理圈 ID(c3c7e66 新增) | 数据记录链路标识;起批时透传给 `Trust_Track` |
| `quality` | **航迹质量(0~1,V1.5.8 新增)** | 每延长一点即重算(见 8.6);起批时取末点值作为初始质量,并参与起批质量门限判断 |
| `temp_track_section_idx` | 临时航迹区号 | 相关赋值已被注释停用,当前不参与逻辑 |
> 字段更名提示(c3c7e66):`snr`→`SNR`、`Amp`→`amplitude`、`T`→`CPI_time`、`Temp_track_section_idx`→`temp_track_section_idx`。阅读早期提交或旧文档时注意对应关系。
@@ -113,9 +116,9 @@ track_init.track_init_process_logic(&point_recv, &trust_track, &temp_track,
| `temp_track` | 输入/输出 | 临时航迹表(跨圈状态) |
| `Trust_Track_Output[MAX_TRACK_NUM][10]` | 输出 | 与 `track_asso` **共用同一数组与同一计数**;起始流程在 `track_asso` 已写入的行之后追加新航迹的历史点 |
| `Trust_track_num_Output` | 输出 | 累计行数(track_asso 输出 N 行后,track_init 在其后继续追加) |
| `Work_Parameter` | 输入 | 运行时雷达参数(`RadarPara`) |
| 全局 `rdp_parameter` | 读取 | 工作参数(TOML 配置加载的全局对象,`struct.h` 声明、`data_process.cpp` 定义);**不再经函数形参传入**,模块直接访问 |
> **输出行数核算**:`track_asso` 先为当圈**有更新**的可靠航迹写 N 行(输出循环判 `Track_Update_Flag==1` 并清零,即只含关联到实点或成功外推的航迹);`track_die` 随后删除 D 条(已写出的行与计数不变);起始流程每次起批前判 `trust_track->size() < MAX_TRACK_INDEX(500)`,故最多再追加 `499 − N + D` 行,**合计最多 499 + D 行**。正常情况下 D 很小,远小于 500;但理论上当 D ≥ 2 且当圈起批数量恰好占满配额(需数百条同时起批)时,行号会超过第一维容量 500,存在越界写入风险(极端场景,见 15.14)。第二维 `[j]`(j < L ≤ 10)始终安全。代价是可靠航迹数达 500 时不再允许新起批。
> **输出行数核算**:`track_asso` 先为当圈**有更新**的可靠航迹写 N 行(输出循环判 `Track_Update_Flag==1` 并清零,即只含关联到实点或成功外推的航迹);`track_die` 随后删除 D 条(已写出的行与计数不变);起始流程每次起批前判 `trust_track->size() < track_index_max`(配置项,默认 500),故最多再追加 `499 − N + D` 行,**合计最多 499 + D 行**。正常情况下 D 很小,远小于 500;但理论上当 D ≥ 2 且当圈起批数量恰好占满配额(需数百条同时起批)时,行号会超过第一维容量 500,存在越界写入风险(极端场景,见 15.14)。第二维 `[j]`(j < L ≤ 10)始终安全。代价是可靠航迹数达 `track_index_max` 时不再允许新起批。
---
@@ -156,7 +159,7 @@ for (size_t i=0;i<(*point_recv).size();i++)
```
- 上游 `track_asso` 已把关联上的点从 `point_recv` 中删除,所以这里进来的都是「未关联点」;函数内部**不再**用 `Use_Flag` 过滤输入(`Use_Flag` 只用于本次调用内部两阶段关联的标记)。
- 点迹的 `Range` 已在凝聚阶段做过 `[R_MIN=100, R_MAX=100000] m` 量程过滤。
- 点迹的 `Range` 已在凝聚阶段做过量程过滤(下限/上限为配置项 `point_cohesion_range_min` / `point_cohesion_range_max`,默认 100 m / 100000 m)。
### 4.2 buff_round 自增(L40-47)
@@ -307,13 +310,13 @@ $$
| 条件 | 含义 |
|---|---|
| `dis ≤ V_max·ΔT` | 平均速度不超过最大速度(`V_max = Work_Parameter.V_MAX`) |
| `dis ≤ V_max·ΔT` | 平均速度不超过最大速度(`V_max = rdp_parameter.track_init_velocity_max`,配置项) |
| `dis ≥ V_min·ΔT` | 平均速度不低于最小速度(滤除静止/慢速点) |
| `v_r,p · v_r,h > 0` | 两点多普勒径向速度**同号**(同接近/同远离) |
| `dis ≥ abs(v_r,p)·ΔT` | 位置变化不小于**点迹自身多普勒**在这段时间内的径向位移(多普勒-几何一致性) |
| `dis ≥ abs(v_r,h)·ΔT` | 同上,用航迹头的多普勒约束 |
**近/远程分支(L348-356)当前是死分支**:`work_mode==0` 与 `else` 都取 `vmax = Work_Parameter.V_MAX`(注释"近程模式最大速度减小一点"未生效;原 `V_MAX/5` 写法仅保留在未启用的 `track_init_direct_tracking.cpp` 注释里)。
**近/远程分支(L348-356)当前是死分支**:`work_mode==0` 与 `else` 都取 `vmax = rdp_parameter.track_init_velocity_max`(注释"近程模式最大速度减小一点"未生效;原按 `V_MAX/5` 收紧的写法仅保留在未启用的 `track_init_direct_tracking.cpp` 注释里)。
**ΔT 语义**:判据隐含要求 `ΔT > 0`(若 `ΔT < 0`,则 `dis ≤ 负数` 必假)。`ΔT == 0` 时仅在 `dis == 0`(同时间同位置的点)才可能成立,属退化情形。
@@ -379,8 +382,11 @@ for (...) (*point_recv).push_back(point_process[i]); // 回写未被使用的
int L = (*Iter).size();
double range = (*Iter)[L-1].r;
double azi = (*Iter)[L-1].azi/PI*180; // 转度,供屏蔽区比较
double quality = (*Iter)[L-1].quality; // V1.5.8:末点航迹质量
if( (L <= 10 && L >= Work_Parameter.track_start_point_num) && track_init_prohibit(range, azi, Work_Parameter)==0 )
if( L <= 10 && L >= rdp_parameter.track_init_point_num_min
&& track_init_prohibit(range, azi) == 0
&& quality >= 按长度取的质量门限 ) // V1.5.8 新增
{ 复制到 Track_to_start; 从 temp_track 中 erase; }
else
Iter++;
@@ -388,17 +394,18 @@ else
**入选条件**:
1. **长度**:`track_start_point_num ≤ L ≤ 10`(`track_start_point_num` 由宿主经 `track_process_parameters_initial` 传入,接口层校验范围 **3~9**,`data_process.cpp` L208);
2. **不在起批屏蔽区**内(用**末点**的距离/方位判断,见 8.5)。
1. **长度**:`track_init_point_num_min ≤ L ≤ 10`(配置项 `[track_init] track_init_point_num_min`,由 TOML 配置文件提供,加载器校验范围 **3~9**;改造前由宿主经 `track_process_parameters_initial` 传入);
2. **不在起批屏蔽区**内(用**末点**的距离/方位判断,见 8.5);
3. **航迹质量达标(V1.5.8,d9a7bd0)**:按长度取门限 `track_init_quality_L3~L7`(配置项)——L=3 →0.7、L=4 →0.6、L=5 →0.5、L=6 →0.4、L≥7 →0.3;比较对象是**末点** `quality`,不达标则本圈不起批、留在 `temp_track` 继续积累。
命中即从 `temp_track` 摘出(先复制后删除,`Track_to_start` 成为候选表)。
> 结果:**一旦长度达到阈值且不落在屏蔽区,当圈即起批**(没有"连续 M 圈满足才起批"的确认期)。若因屏蔽区被拦住,航迹会继续在 `temp_track` 中增长,直到离开屏蔽区(此时 `L > track_start_point_num`,见 8.3 的副作用)或达到 `L=10` 被判消亡。
> 结果:**一旦长度达到阈值、不落在屏蔽区且末点质量达标,当圈即起批**(没有"连续 M 圈满足才起批"的确认期)。若因屏蔽区被拦住,航迹会继续在 `temp_track` 中增长,直到离开屏蔽区(此时 `L > track_init_point_num_min`,见 8.3 的副作用)或达到 `L=10` 被判消亡;若因质量门限被拦住,则继续积累直到质量回升或临时航迹消亡。
### 8.2 步骤 2:同源去重(L504-550)
```cpp
int L = Work_Parameter.track_start_point_num; // 注意:此 L 是"起批点数",不是航迹长度
int L = rdp_parameter.track_init_point_num_min; // 注意:此 L 是"起批点数",不是航迹长度
for (i: 0 .. size-2) for (j: i+1 .. size-1) {
if (两者都未被标记 asso_flag==2) {
if (某条长度 < 3) continue; // 防御性判断(实际不会触发)
@@ -412,7 +419,7 @@ for (i: 0 .. size-2) for (j: i+1 .. size-1) {
```
- **同源判定**:两条候选航迹的前三个点中,只要有一个点的 `T` 与 `r` 完全相等(同一点迹被复制而来),就视为同源重复;
- **择优准则**:比较 `[L-1].d`(第 `track_start_point_num` 个点的关联统计距离),**保留 d 更小者**;
- **择优准则**:比较 `[L-1].d`(第 `track_init_point_num_min` 个点的关联统计距离),**保留 d 更小者**;
- **标记位置**:只写 `[0].asso_flag = 2`,最后统一擦除;
- 去重仅看**前三点**,且无论航迹实际多长都用固定下标 0/1/2。
@@ -420,7 +427,7 @@ for (i: 0 .. size-2) for (j: i+1 .. size-1) {
```cpp
if (Track_to_start[i].size() < 3) continue; // 防御
if (trust_track->size() < MAX_TRACK_INDEX) { // 500 条上限
if (trust_track->size() < rdp_parameter.track_index_max) { // 默认 500 条上限
int index = track_index_mangement.track_ind_get(trust_track);
if (index > 0) { ... }
}
@@ -434,7 +441,7 @@ T1 = (点[L-2].T − 点[L-3].T)/1000.0; T2 = (点[L-1].T − 点[L-2].T)/1000
Kalman.kalman_filter_init_3dots(Z0, Z1, Z2, T1, T2, X, P); // 6 维 [x,vx,ax,y,vy,ay]
```
> ⚠ **不一致点**:初始化用「最后三点」,而输出历史点(8.4)只写「前 `L` 个点」。当航迹长度 = `track_start_point_num` 时两者一致;当因屏蔽区滞留使长度 > 阈值时,输出的历史点里**不包含**用于初始化的那三个点。
> ⚠ **不一致点**:初始化用「最后三点」,而输出历史点(8.4)只写「前 `L` 个点」。当航迹长度 = `track_init_point_num_min` 时两者一致;当因屏蔽区滞留使长度 > 阈值时,输出的历史点里**不包含**用于初始化的那三个点。
**Trust_Track 字段构造(L573-608)**:
@@ -447,37 +454,60 @@ Kalman.kalman_filter_init_3dots(Z0, Z1, Z2, T1, T2, X, P); // 6 维 [x,vx,ax,y
| `T_track` / `GNSS_time` / `Height` | 取**末点**的对应值(`CPI_time` 字段) |
| `Amplitude / snr_point / RCS / pitch_num / speed_dim / range_dim` | 末点透传 |
| `track_id / point_id / circle_id` | 取末点的 `temp_track_id / point_id / circle_id`(记录链路标识,b227ae1 新增) |
| `Track_fingerprint` | **航迹指纹(V1.5.9,0f3da99)**:`computeFingerprint(末点.r, 末点.azi, 末点.vr, 末点.RCS, 末点.CPI_time)`,16 bit(量化 0.01 m / 0.001° / 0.01 m·s⁻¹ / 0.1 dBsm / 1 ms → splitmix64 混合 → 折叠 16 bit),TAS 波束与 TAS 输入均以它定位航迹(见 README 9.12) |
| `point_flag = 1` | 实点 |
| `Track_Mode = 0` | TWS |
| `Target_Type = UNCONF_TARGET(0)` | 未确认目标 |
| `Target_Type = 0` | 未确认目标(`UNCONF_TARGET` 宏已删除,直接写 0) |
| `Extrapolate_round = 0` | 外推计数清零 |
| `approach_flag = -1` | 接近标志复位 |
| `manual_delete_flag / manual_tracking_flag / associate_point_number / point_type / primary_target_flag = 0` | 各类标志复位(`Trust_Track trust_track_tmp = {}` 零初始化,故新航迹的重点目标标记为 0) |
> L606-608 有一处**死代码**:`coor_trans::cart2polar(X[0], X[3], &r, &azi)`(把 `X[3]=vy` 当 y 用),计算结果 `r/azi` 从未被使用。
**航迹号分配**(`track_index_mangement.cpp`):空表返回 1;否则从 `lastest_index+1` 向上找第一个未被占用的号(含 500),找不到再从 1 回绕;全满返回 0(**不起批**,该候选被静默丢弃)。
**航迹号分配**(`track_index_mangement.cpp`):空表返回 1;否则从 `lastest_index+1` 向上找第一个未被占用的号(含 `track_index_max`,默认 500),找不到再从 1 回绕;全满返回 0(**不起批**,该候选被静默丢弃)。
### 8.4 输出更新航迹(L612-664)
- `*Trust_track_num_Output += 1`,在 `track_asso` 已写的行之后追加一行;
- 对 `j = 0 .. L-1`(`L = track_start_point_num`)写入该航迹的**历史点**:`Track_Index`、`Point_Sum = L`,以及每个点的距离/方位/俯仰、速度、幅度/SNR/RCS、俯仰波位、十字星数据、航向角、时间、直角坐标、原始点信息等(完整映射见第 14 节);
- 对 `j = 0 .. L-1`(`L = rdp_parameter.track_init_point_num_min`)写入该航迹的**历史点**:`Track_Index`、`Point_Sum = L`,以及每个点的距离/方位/俯仰、速度、幅度/SNR/RCS、俯仰波位、十字星数据、航向角、时间、直角坐标、原始点信息等(完整映射见第 14 节);
- 之后该航迹的后续更新由 `track_asso`(每次 `Point_Sum=1`)负责。
### 8.5 起批屏蔽区(L699-712)
```cpp
for (i = 0; i < Work_Parameter.track_prohibite_area_num; i++)
if (r < R_max_track_prohibited[i] && r > R_min_track_prohibited[i] &&
azi < Azimuth_max_track_prohibited[i] && azi > Azimuth_min_track_prohibited[i])
for (i = 0; i < rdp_parameter.track_init_prohibite_area_num; i++)
if (r < rdp_parameter.track_init_prohibite_area_range_max[i] && r > rdp_parameter.track_init_prohibite_area_range_min[i] &&
azi < rdp_parameter.track_init_prohibite_area_azimuth_max[i] && azi > rdp_parameter.track_init_prohibite_area_azimuth_min[i])
return 1; // 位于屏蔽区,禁止起批
return 0;
```
- 判据是**距离-方位矩形区域**(`azi` 已转成度),最多 30 个(`RadarPara` 数组长度;数量未做上限校验);
- 判据是**距离-方位矩形区域**(`azi` 已转成度),最多 30 个(`RDP_parameter` 定长数组长度;配置入口由加载器校验 `*_num ∈ [0,30]` 且 4 个数组长度等于 `*_num`,非法配置在 `parameters_reload` 阶段即失败);
- 采用**严格不等号**:恰好落在边界上的点不算屏蔽;
- 判断用**末点**极坐标,不使用高度。
### 8.6 临时航迹质量评估与起批门限(V1.5.8,1ae0cf2 / d9a7bd0)
在阶段二(点-临时航迹关联)每成功延长一个点后,`point_temp_track_asso()` 立即重算该临时航迹末点的质量:
```cpp
L = temp_track_ref.size();
d_tmp = exp(-temp_track_ref[L-1].d); // 末点统计距离项(d 已是马氏距离平方)
// L == 3:取三个点 SNR 的平均;L > 3:只取末点 SNR
snr_tmp = 1 / (1 + exp(-(SNR - 16) / 1.2)); // 以 16 dB 为界的 Sigmoid
// 权重为配置项 track_quality_d_weight(0.5) / track_quality_snr_weight(0.3)
quality_tmp = (w_d * d_tmp + w_snr * snr_tmp) / (w_d + w_snr); // 只用距离 + SNR 两项(不含长度项)
if (L == 3) 三点全部写 quality_tmp; // 质量起点
else 末点 quality = 前一点 quality * c + quality_tmp * (1 - c); // 指数平滑,c = track_quality_iter_coeff(0.8)
```
- `Temp_track::quality` 是 **0~1** 的归一化值;起批时 `Trust_Track.Quality = 末点 quality`,`Point_sum = Valid_point_sum = L`;
- 起批门限按**临时航迹长度 L** 取(比较对象是**末点**质量):L=3 → 0.7、L=4 → 0.6、L=5 → 0.5、L=6 → 0.4、L≥7 → 0.3;
- 质量不达标时该临时航迹**不进入 `Track_to_start`**,继续留在 `temp_track` 中参与下一圈关联(受 `buff_round`/长度 10 的消亡约束)。
> 注意:质量只在「点-临时航迹关联」路径更新,阶段三(点 × 1 点航迹头 → 2 点临时航迹)不写 `quality`;2 点航迹要到下一圈被延长成 3 点时才会首次获得质量值,这与其本来也不能起批(长度不足)是一致的。
---
## 9. 阶段六:临时航迹消亡
@@ -507,26 +537,28 @@ if ((*Iter)[n-1].buff_round > 2 || n >= 10) erase;
|---|---|
| **0. 输入资格** | 该点未被 `track_asso` 关联(已从 `point_recv` 删除);凝聚后 `100 m ≤ r ≤ 100000 m` |
| **1. 生成航迹头** | 未被阶段二/三消费(`Use_Flag==0`);创建时 `buff_round=1`(L77-114) |
| **2. 航迹头 → 2 点**(L325, L368-372) | 航迹头 `size==1` 且 `buff_round ≥ 2`;`dis ≤ V_MAX·ΔT`;`dis ≥ V_MIN·ΔT`;`dis ≥ abs(vr_p)·ΔT`;`dis ≥ abs(vr_h)·ΔT`;`vr_p·vr_h > 0`(隐含 `ΔT > 0`) |
| **3. 2 点 → 3 点及以上**(L164, L191, L221) | 临时航迹 `size > 1` 且末点 `buff_round ≥ 2`;`abs(r_p − r_末点) ≥ 5 m`;`d ≤ 8`(代码写 `d² ≤ 64`);`α < 60°`;`vr_p·vr_末点 > 0` |
| **4. 转可靠航迹**(L484, L556) | `track_start_point_num ≤ L ≤ 10`;末点不在起批屏蔽区;同源去重后保留;`trust_track.size() < 500` 且成功分配航迹号 |
| **2. 航迹头 → 2 点**(L325, L368-372) | 航迹头 `size==1` 且 `buff_round ≥ 2`;`dis ≤ track_init_velocity_max·ΔT`;`dis ≥ track_init_velocity_min·ΔT`;`dis ≥ abs(vr_p)·ΔT`;`dis ≥ abs(vr_h)·ΔT`;`vr_p·vr_h > 0`(隐含 `ΔT > 0`) |
| **3. 2 点 → 3 点及以上**(L164, L191, L221) | 临时航迹 `size > 1` 且末点 `buff_round ≥ 2`;`abs(r_p − r_末点) ≥ 5 m`;`d ≤ 8`(代码写 `d² ≤ 64`,`track_init_threshold=8`);`α < 60°`(`track_init_angle`);`vr_p·vr_末点 > 0` |
| **4. 转可靠航迹**(L484, L556) | `track_init_point_num_min ≤ L ≤ 10`;末点不在起批屏蔽区;**末点质量 ≥ 按 L 取的门限(V1.5.8)**;同源去重后保留;`trust_track.size() < track_index_max` 且成功分配航迹号 |
| **5. 消亡**(L686) | `末点.buff_round > 2`(连续 2 圈无新点)或 `长度 ≥ 10` |
### 10.2 门限汇总
| 判据 | 数值 | 来源 | 可调 |
|---|---|---|---|
| 最大速度 `V_MAX` | 运行时 | `RadarPara.V_MAX` | ✅ |
| 最小速度 `V_MIN` | 运行时 | `RadarPara.V_MIN` | ✅ |
| 最大速度 `vmax` | 50 m/s(样例) | 配置项 `track_init.track_init_velocity_max` | ✅(改配置文件) |
| 最小速度 `vmin` | 1 m/s(样例) | 配置项 `track_init.track_init_velocity_min` | ✅(改配置文件) |
| 距离差下限 `abs(Δr)` | **5 m(硬编码)** | `track_init.cpp` L191 | ❌ |
| 起批波门 `d ≤ 8` | `TRACK_START_THRESHOLD=8` | `parameters.h` L49/L98 | 编译期 |
| 起批折角 | `ALPHA_START=60°` | `parameters.h` L50/L99 | 编译期 |
| 起批点数 | 3~9(典型 3~4) | `RadarPara.track_start_point_num` | ✅ |
| 起批波门 `d ≤ 8` | `track_init_threshold=8` | 配置项 `track_init.track_init_threshold` | ✅(改配置文件) |
| 起批折角 | `track_init_angle=60°` | 配置项 `track_init.track_init_angle` | ✅(改配置文件) |
| 起批点数 | 3~9(典型 3~4) | 配置项 `track_init.track_init_point_num_min` | ✅(加载器校验 3~9) |
| 临时航迹长度上限 | 10(硬编码) | L484 / L686 | ❌ |
| 允许丢圈数 | 1 圈(`buff_round > 2` 消亡) | L686 | ❌ |
| 航迹头上限等待 | 2 圈 | L686(1 点航迹同判据) | ❌ |
| 起批屏蔽区 | ≤30 个矩形 | `RadarPara.track_prohibite_area_*` | ✅ |
| 航迹数上限 | 500 | `MAX_TRACK_INDEX` | 编译期 |
| 起批屏蔽区 | ≤30 个矩形 | 配置段 `[track_init_prohibite_area]`(数组长度 = `*_num`) | ✅(加载器校验 0~30) |
| 起批最低质量(V1.5.8) | L3→0.7 / L4→0.6 / L5→0.5 / L6→0.4 / L≥7→0.3 | 配置项 `track_init.track_init_quality_L3~L7` | ✅(改配置文件) |
| 质量权重(V1.5.8) | 距离 0.5 / SNR 0.3 / 长度 0.2 | 配置段 `[track_quality]` | ✅(改配置文件) |
| 航迹数上限 | 500(默认) | 配置项 `track_index.track_index_max` | ✅(加载器校验 1~500) |
---
@@ -587,7 +619,7 @@ $$
\alpha=\arccos\!\left(\frac{\vec D_{12}\cdot\vec D_{23}}{\|\vec D_{12}\|\,\|\vec D_{23}\|}\right)\cdot\frac{180}{\pi}
$$
其中 `D12 = (末点 − 前一点)`、`D23 = (新点 − 末点)`,三点均取 **Temp_track 的直角坐标**(`X[0]` 为 x、`X[2]` 为 y)。`α` 越小表示越接近直线;`ALPHA_START=60°` 表示允许的航向偏折。
其中 `D12 = (末点 − 前一点)`、`D23 = (新点 − 末点)`,三点均取 **Temp_track 的直角坐标**(`X[0]` 为 x、`X[2]` 为 y)。`α` 越小表示越接近直线;`track_init_angle`(配置项,样例 60°)表示允许的航向偏折。
> 无 `acos` 定义域钳位:当两点重合(分母为 0)或浮点误差使比值略超 ±1 时结果为 `NaN`,`NaN < 60` 恒为 false → 表现为「该点无法关联」,不崩溃。
@@ -597,14 +629,14 @@ $$
### 12.1 与 `README.md` 第 8.3 / 9.3 节的差异(README 已同步本节结论)
> 说明:下表为本文档成文时发现的差异,当时 README 尚未同步 7d5a56f 的改动;随后已按本节结论更新 README 第 2、7.2、8.1、8.3、9.3、9.6、10.1 等节,「README 描述」列对应的是更新前的旧表述,保留以说明改动缘由。
> 说明:下表为本文档成文时发现的差异,当时 README 尚未同步 7d5a56f 的改动;随后已按本节结论更新 README 第 2、7.2、8.1、8.3、9.3、9.6、10.2 等节,「README 描述」列对应的是更新前的旧表述,保留以说明改动缘由。
| 项 | README 描述(更新前) | 当前源码 |
|---|---|---|
| 点-航迹头判据 | 按距离分段:近程 `r≥1000` 全速 / `r<1000` 半速;中远程 `r≥2000` 全速 / `r<2000` 1/2~1/3 速 | 该分支已**整体注释停用**,改为统一 `V_MIN·ΔT ≤ dis ≤ V_MAX·ΔT` **外加两条多普勒一致性条件** `dis ≥ abs(vr_p)·ΔT`、`dis ≥ abs(vr_h)·ΔT`(L361-372) |
| 起批触发长度 | 长度满足 `track_start_point_num ≤ L ≤ 10` | 一致 |
| 初始化用点 | 最近三点 | 不一致:取下标 `track_start_point_num-3 ~ -1` 的三个点,航迹长度大于起批点数时**不是**航迹真正的末三点(详见第 15 节第 2 条) |
| 输出历史点 | "将临时航迹的全部 L 个点写入" | 实际只写**前 `track_start_point_num` 个点**(L614 的 `L` 是起批点数,不是航迹长度) |
| 点-航迹头判据 | 按距离分段:近程 `r≥1000` 全速 / `r<1000` 半速;中远程 `r≥2000` 全速 / `r<2000` 1/2~1/3 速 | 该分支已**整体注释停用**,改为统一 `track_init_velocity_min·ΔT ≤ dis ≤ track_init_velocity_max·ΔT` **外加两条多普勒一致性条件** `dis ≥ abs(vr_p)·ΔT`、`dis ≥ abs(vr_h)·ΔT`(L361-372) |
| 起批触发长度 | 长度满足 `track_init_point_num_min ≤ L ≤ 10` | 一致 |
| 初始化用点 | 最近三点 | 不一致:取下标 `track_init_point_num_min-3 ~ -1` 的三个点,航迹长度大于起批点数时**不是**航迹真正的末三点(详见第 15 节第 2 条) |
| 输出历史点 | "将临时航迹的全部 L 个点写入" | 实际只写**前 `track_init_point_num_min` 个点**(L614 的 `L` 是起批点数,不是航迹长度) |
| use_flag 过滤 | "track_init 跳过 Use_Flag==1 的点" | 上游 `track_asso_process()` 已删除 `Use_Flag==1` 的点迹,`track_init` 入口(L59-70)也会再剔除一次,两道处理叠加,描述成立 |
### 12.2 与 `track_init_direct_tracking.cpp`(引导跟踪版,未启用)的差异
@@ -612,8 +644,8 @@ $$
| 项 | `track_init.cpp`(TWS,启用) | `track_init_direct_tracking.cpp`(未启用) |
|---|---|---|
| 入口 | 完整实现 | `track_init_process_logic` 空实现(L11-23) |
| 点-航迹头判据 | 统一门限 + 两条多普勒一致性条件 | 仍为分距离段门限(`V_MAX/5` 亦在其注释中) |
| 起批长度 | `L ≥ track_start_point_num`(长度可大于阈值) | `L == track_start_point_num`(严格相等) |
| 点-航迹头判据 | 统一门限 + 两条多普勒一致性条件 | 仍为分距离段门限(原 `V_MAX/5` 亦在其注释中) |
| 起批长度 | `L ≥ track_init_point_num_min`(长度可大于阈值) | `L == track_init_point_num_min`(严格相等) |
| 去重 | 有同源去重 | 无 |
| 屏蔽区 | 有 | 无 |
| 航迹号 | `track_ind_get()` 分配 | 外部传入 `track_ID` |
@@ -624,44 +656,44 @@ $$
## 13. 参数与调参
### 13.1 编译期宏(`parameters.h`)
> **参数配置方式改造后**:与起始流程相关的全部可调参数都在 TOML 配置文件里(样例 `config/config_tmp.toml`),由公开接口 `parameters_reload(配置文件路径)` 加载到 `RDP_parameter`;`parameters.h` 不再有这些宏,宿主也不再下发 `RadarPara`。改参数 = 改配置文件后重新调用 `parameters_reload()`(加载失败时参数保持不变)。完整参数表与严格校验规则见 README 第 10 节。
| 宏 | 相扫/机扫 | 含义 | 调大效果 |
### 13.1 配置项(样例值,均为「配置段.键」)
| 配置项 | 样例值 | 含义 | 调大效果 |
|---|---|---|---|
| `TRACK_START_THRESHOLD` | 8 / 8 | 起批波门,实际生效为 `d ≤ 8` | **更容易起批**(虚警增多) |
| `ALPHA_START` | 60 / 60 | 起批折角(度) | 更容易起批(允许更急的转向) |
| `SIGMA_R / SIGMA_A / SIGMA_V` | 10 / 0.02 / 2.0 | EKF 量测噪声 | 影响 `S` 与 `d`,进而影响关联难度 |
| `MAX_TRACK_INDEX` | 500 | 航迹号/航迹数上限 | — |
| `R_MIN / R_MAX` | 100 / 100000 | 有效量程(凝聚阶段过滤) | — |
| `PI` | 3.1415926**f** | 角度换算(float 精度) | — |
| `track_init.track_init_threshold` | 8 | 起批波门,实际生效为 `d ≤ 8` | **更容易起批**(虚警增多) |
| `track_init.track_init_angle` | 60 | 起批折角(度) | 更容易起批(允许更急的转向) |
| `track_associate.sigma_range/azimuth/pitch/velocity` | 10 / 0.02 / 0.2 / 2.0 | EKF 量测噪声 | 影响 `S` 与 `d`,进而影响关联难度 |
| `track_index.track_index_max` | 500 | 航迹号/航迹数上限(1~500) | — |
| `cohesion.point_cohesion_range_min/max` | 100 / 100000 | 有效量程(凝聚阶段过滤) | — |
| `track_init.track_init_point_num_min` | 3 | 起批点数(加载器校验 3~9):调大 → 起批更慢、更抗虚警,但机动目标捕获更迟 | |
| `track_init.track_init_velocity_max/min` | 50 / 1 | 头-点关联的速度区间;`min` 调大可滤除静止/慢速点 | |
| `radar_params.radar_work_mode` | 0 | 当前对起始流程**无实际影响**(近/远程分支取值相同) | |
| `track_init_prohibite_area.*_num` + 4 个数组 | 0 + 空数组 | 起批屏蔽区(距离 m / 方位 度;≤30 组,数组长度 = `*_num`) | |
| `track_init.track_init_quality_L3~L7` | 0.7 / 0.6 / 0.5 / 0.4 / 0.3 | 按临时航迹长度的起批最低质量(V1.5.8) | 降低 → 更容易起批 |
| `track_quality.*` | 0.5 / 0.3 / 0.2 / 0.8 | 质量权重、平滑系数与衰减系数(V1.5.8) | 影响质量评估与消亡速度 |
### 13.2 运行期参数(`RadarPara`)
> 旧 `RadarPara` 中的 `track_start_threshold` 字段原本就未被实现读取;新结构体只保留实现真正使用/保留的参数(见 README 10.3 对照表)。
| 字段 | 影响 |
|---|---|
| `track_start_point_num` | 起批点数(接口校验 3~9):调大 → 起批更慢、更抗虚警,但机动目标捕获更迟 |
| `V_MAX` / `V_MIN` | 头-点关联的速度区间;`V_MIN` 调大可滤除静止/慢速点 |
| `work_mode` | 当前对起始流程**无实际影响**(近/远程分支取值相同) |
| `track_prohibite_area_num` + 4×30 数组 | 起批屏蔽区(距离 m / 方位 度) |
| `track_start_threshold` | **声明但未被实现读取**(实际用宏 `TRACK_START_THRESHOLD`) |
### 13.2 调参直觉
### 13.3 调参直觉
- **虚警多、起批太容易**:先降 `TRACK_START_THRESHOLD`(8 → 5 左右),再降 `ALPHA_START`;必要时提高 `track_start_point_num`。
- **真实目标起批慢**:提高 `TRACK_START_THRESHOLD` / `ALPHA_START`,或降低 `track_start_point_num`(最小 3)。
- **静止/低速杂波起批**:提高 `V_MIN`。
- **虚警多、起批太容易**:先降 `track_init_threshold`(8 → 5 左右),再降 `track_init_angle`;必要时提高 `track_init_point_num_min`。
- **真实目标起批慢**:提高 `track_init_threshold` / `track_init_angle`,或降低 `track_init_point_num_min`(最小 3)。
- **静止/低速杂波起批**:提高 `track_init_velocity_min`。
- **某方位扇区/距离段禁止起批**(如地物、干扰区):配置起批屏蔽区,比调门限更精准。
- **注意**:`config/config_tmp.toml` 是相扫取值(`scan_mode = 0`);机扫部署时需配 `scan_mode = 1`,且 `extrapolate_max_tas`、`height_smooth_win`、`track_die_round_tas`、`tas_track_num_max` 按机扫取值配置(见 README 4.2、10.1)。
---
## 14. 输出字段映射
对每条新起批航迹的每个历史点 `j = 0 .. track_start_point_num-1`(L614-664):
对每条新起批航迹的每个历史点 `j = 0 .. track_init_point_num_min-1`(L614-664):
| 输出字段 | 来源 | 备注 |
|---|---|---|
| `Track_Index` | 新分配的航迹号 | 同一行内所有点相同 |
| `Point_Sum` | `= track_start_point_num` | 与 `track_asso` 输出的 `Point_Sum=1` 可作区分 |
| `Point_Sum` | `= track_init_point_num_min` | 与 `track_asso` 输出的 `Point_Sum=1` 可作区分 |
| `Range` / `Azimuth` | 点的 `r` / `azi`(→度) | 极坐标 |
| `Elevation` | `asin(h/r)`(度,钳位 ±1) | BUG-27 修复后的定义域钳位 |
| `z` | 点的 `height` | |
@@ -675,7 +707,8 @@ $$
| `GNSS_time` | 点透传 | |
| `Flag_Point` | 恒为 **1**(实点) | |
| `Track_Mode` | 恒为 **0**(TWS) | |
| **未赋值** | `Flag_Direction`、`x_Predict/y_Predict/z_Predict`、`Range_Predict/Azimuth_Predict/Elevation_Predict`、`Target_Type`、`prf_point`、`v_z`、`amp_asv_point`、`prn_fftn_point` | DLL 不清零该数组,宿主读到的是其自身缓存的旧值/未初始化值。注意 `Flag_Direction` 只在关联输出路径赋值(`track_asso.cpp` L129 / `track_asso_tas.cpp` L93),起批输出不写该字段。该字段的判据已由 f249fdc 的 `vr_point > 0 ? 1 : 0`(实测径向速度,受多普勒速度模糊影响符号可能取反)改为 fc49707 + 42b598e 的**航向-位置夹角法**:`Direction_Angle = atan2(X[4], X[1])`(弧度转度后同时输出到 `Direction_Angle`),`x`、`y` 为经 `Work_Parameter.Sys_delay` 补偿后的位置、`r` 为其模,`acos((-x·cos(Direction_Angle) - y·sin(Direction_Angle))/r) < π/2` 判为靠近(1)、否则远离(0),即滤波速度方向与"雷达→目标"位置方向夹角超 90° 判为接近。另 `amp_asv_point` / `prn_fftn_point` 属 e8c56f9 新增,`data_preprocess()` **现已**把 `DataRev.amp_asv / prn_fftn` 搬入 `PointRecv`(TWS 与 TAS 分支均赋值,并随 `RdpRecPoint` 落库),因此 TWS 关联/输出路径的该字段已有有效来源;TAS 关联与输出路径(`track_asso_tas.cpp`)仍未涉及这两个字段,TAS 输出行中它们保持宿主原值 |
| `Quality` | **该历史点的临时航迹质量 × 100**(V1.5.8) | 内部 `quality` 为 0~1,输出折算为 0~100;同一条航迹的各个历史点可能取值不同(L=3 时三点相同,之后只有末点更新) |
| **未赋值** | `Flag_Direction`、`x_Predict/y_Predict/z_Predict`、`Range_Predict/Azimuth_Predict/Elevation_Predict`、`Target_Type`、`prf_point`、`v_z`、`amp_asv_point`、`prn_fftn_point` | DLL 不清零该数组,宿主读到的是其自身缓存的旧值/未初始化值。注意 `Flag_Direction` 只在关联输出路径赋值(`track_asso.cpp` / `track_asso_tas.cpp`),起批输出不写该字段。该字段的判据已由 f249fdc 的 `vr_point > 0 ? 1 : 0`(实测径向速度,受多普勒速度模糊影响符号可能取反)改为 fc49707 + 42b598e 的**航向-位置夹角法**:`Direction_Angle = atan2(X[4], X[1])`(弧度转度后同时输出到 `Direction_Angle`),`x`、`y` 为经配置项 `radar_system_delay` 补偿后的位置、`r` 为其模,`acos((-x·cos(Direction_Angle) - y·sin(Direction_Angle))/r) < π/2` 判为靠近(1)、否则远离(0),即滤波速度方向与"雷达→目标"位置方向夹角超 90° 判为接近。另 `amp_asv_point` / `prn_fftn_point` 属 e8c56f9 新增,`data_preprocess()` **现已**把 `DataRev.amp_asv / prn_fftn` 搬入 `PointRecv`(TWS 与 TAS 分支均赋值,并随 `RdpRecPoint` 落库),因此 TWS 关联/输出路径的该字段已有有效来源;TAS 关联与输出路径(`track_asso_tas.cpp`)仍未涉及这两个字段,TAS 输出行中它们保持宿主原值 |
---
@@ -684,19 +717,21 @@ $$
> 以下为阅读源码得到的观察,**不代表已确认的缺陷**,部分为有意保留的历史行为;修改前请结合实测数据评估。
1. **硬编码 5 m 距离差门限**(L191):`|r_p − r_末点| < 5` 的点永不参与关联。对切向飞行、远距离慢速目标可能造成漏关联(起批失败)。
2. **初始化用点不是航迹真正的末三点**(L505、L563-565 vs L614):起批流程中 `L = track_start_point_num`(L505 覆盖了前面循环里的航迹长度变量),Z0/Z1/Z2 取下标 `L-3 / L-2 / L-1`,输出循环也只写到 `j = L-1`。因此当航迹长度 N 大于起批点数时,参与初始化的只是**前 L 个点中的最后三点**,而不是航迹真正的末三点;第 L 个点之后的尾部点既未参与初始化也不出现在输出中(这些点通常来自滞留在起批屏蔽区期间的积累)。
3. **去重只比较前三个点**(L517-519 硬编码 0/1/2),且优度指标取 `[track_start_point_num-1].d`(长度大于阈值时非末点);若 `track_start_point_num > 3`,共享前三点但后续分叉的两条航迹会被误判为同源。
2. **初始化用点不是航迹真正的末三点**(L505、L563-565 vs L614):起批流程中 `L = track_init_point_num_min`(L505 覆盖了前面循环里的航迹长度变量),Z0/Z1/Z2 取下标 `L-3 / L-2 / L-1`,输出循环也只写到 `j = L-1`。因此当航迹长度 N 大于起批点数时,参与初始化的只是**前 L 个点中的最后三点**,而不是航迹真正的末三点;第 L 个点之后的尾部点既未参与初始化也不出现在输出中(这些点通常来自滞留在起批屏蔽区期间的积累)。
3. **去重只比较前三个点**(L517-519 硬编码 0/1/2),且优度指标取 `[track_init_point_num_min-1].d`(长度大于阈值时非末点);若 `track_init_point_num_min > 3`,共享前三点但后续分叉的两条航迹会被误判为同源。
4. **无一对一关联约束**:一个点可同时为多条临时航迹续点(阶段二/三),产生航迹分裂,虚警场景下临时航迹表可能快速膨胀(靠去重与消亡收敛)。
5. **ΔT ≤ 0 无显式校验**(BUG-33 遗留):头-点关联靠 `dis ≤ V_MAX·ΔT` 隐式排除负 ΔT;EKF 关联路径 `d_cal_track_init_EKF(ΔT<0)` 会得到无物理意义的结果,只是通常会被 `d ≤ 8` 挡住。
6. **判据对马氏距离平方二次平方**(L221):实际门限为 `d ≤ 8`(3 自由度约 95.4%)。若按字面 `d² ≤ 64` 理解会误判为 `d ≤ 64`(极宽松)。修改 `TRACK_START_THRESHOLD` 时务必记得这一层。
5. **ΔT ≤ 0 无显式校验**(BUG-33 遗留):头-点关联靠 `dis ≤ track_init_velocity_max·ΔT` 隐式排除负 ΔT;EKF 关联路径 `d_cal_track_init_EKF(ΔT<0)` 会得到无物理意义的结果,只是通常会被 `d ≤ 8` 挡住。
6. **判据对马氏距离平方二次平方**(L221):`track_init_threshold=8` 时实际门限为 `d ≤ 8`(3 自由度约 95.4%)。若按字面 `d² ≤ 64` 理解会误判为 `d ≤ 64`(极宽松)。修改 `track_init_threshold` 时务必记得这一层。
7. **两点/三点初始化无 `T > 0` 保护**(BUG-18):`T` 极小(重复/乱序时间戳)时速度、加速度与协方差会爆炸性放大。
8. **死代码**:L72-75 的 `point_recv` 回写随即被 L117-118 清空;L606-608 的 `cart2polar` 结果未使用;L348-356 的近/远程分支取值相同。
9. **`acos` 无定义域钳位**(L452):浮点越界产生 `NaN`,表现为条件恒假(静默丢弃该关联),不报错也不崩溃。
10. **航迹数饱和静默丢弃**:`trust_track.size() ≥ 500` 或 `track_ind_get()` 返回 0 时,候选被直接跳过且该临时航迹已从 `temp_track` 摘除 → 起批机会永久丢失。
10. **航迹数饱和静默丢弃**:`trust_track.size() ≥ track_index_max`(配置项,默认 500)或 `track_ind_get()` 返回 0 时,候选被直接跳过且该临时航迹已从 `temp_track` 摘除 → 起批机会永久丢失。
11. **未赋值输出字段**(第 14 节):宿主若直接使用 `x_Predict`、`Flag_Direction`、`Target_Type` 等字段,会读到非本次写入的数据。
12. **`PI` 为 float 常量**(3.1415926f)参与双精度运算,角度换算存在约 1e-7 量级偏差,属既有实现风格。
13. **精度/量纲提醒**:`Temp_track::vr` 直接取点的 `Velocity`(多普勒)作为径向速度,EKF 关联时用于残差折叠(盲速 `v_bind`)与符号判据;若宿主的 `Velocity` 存在模糊,符号判据 `vr_p·vr_末点 > 0` 可能失效。
14. **输出数组行号可能越界(极端场景)**:容量判据用 `trust_track->size() < 500`,而 `Trust_Track_Output` 的行计数已在 `track_asso` 阶段按「消亡前」的航迹数推进,中间的 `track_die` 删除 D 条后腾出的配额会让起始流程多写 D 行;当 D ≥ 2 且当圈起批数占满配额时,写入行号可超过 `MAX_TRACK_NUM=500`(详见 2.3)。建议把判据改为同时校验 `*Trust_track_num_Output < MAX_TRACK_NUM` 与 `trust_track->size() < MAX_TRACK_INDEX`。
14. **输出数组行号可能越界(极端场景)**:容量判据用 `trust_track->size() < track_index_max`,而 `Trust_Track_Output` 的行计数已在 `track_asso` 阶段按「消亡前」的航迹数推进,中间的 `track_die` 删除 D 条后腾出的配额会让起始流程多写 D 行;当 D ≥ 2 且当圈起批数占满配额时,写入行号可超过 `MAX_TRACK_NUM=500`(详见 2.3)。建议把判据改为同时校验 `*Trust_track_num_Output < MAX_TRACK_NUM` 与 `trust_track->size() < track_index_max`。
15. **起批质量门限比较的是「末点」质量而非整条航迹质量(V1.5.8)**:`tmp_track_to_trust_track()` 取 `(*Iter)[L-1].quality` 与按长度取的门限比较,而该质量在 L>3 时只是末点的指数平滑值(L=3 时三点同值),并不代表整条临时航迹的平均质量;长度越大门限越低,实际是「用更宽松的门限校验最新一点」。
16. **起批质量来源依赖阶段二**:`quality` 只在「点-临时航迹关联」成功后更新,阶段三产生的 2 点临时航迹不带质量值;长度 3 的临时航迹在第三次延长时写入三点同值,因此其起批质量有定义。若将来把起批点数放宽到 3 以下(当前加载器校验 3~9),则会出现质量恒为 0、永远无法通过质量门限的情况。
---
@@ -752,6 +787,6 @@ $$
| `kalman.cpp` L780-789 | `Bind_speed` | 盲速计算 |
| `coor_trans.cpp` L8-20 | `polar2cart` / `cart2polar` | 坐标转换 |
| `track_index_mangement.cpp` L14-77 | `track_ind_get` | 航迹号分配 |
| `data_process.cpp` L51 | `data_preprocess` | 一圈完成 → `model=1` |
| `data_process.cpp` L154-159 | `track_process` | 起始流程调用点 |
| `data_process.cpp` L204-222 | 参数初始化/修改 | 校验 `track_start_point_num ∈ [3,9]` |
| `data_process.cpp` L22 | `data_preprocess` | 一圈完成 → `model=1` |
| `data_process.cpp` L167 起 | `track_process` | 起始流程调用点(L187-201 调用 `track_asso`/`track_die`/`track_init`) |
| `data_process.cpp` L246-258 | 参数加载 | `parameters_reload()`:TOML 配置 → 全局对象 `rdp_parameter`(原「参数初始化/修改」已删除) |
-16
View File
@@ -1,19 +1,3 @@
# 雷达数据处理项目修改说明
## 概述
新增一个数据记录调试功能,将各类结构体数据写入到数据库中,供后续查询分析。
## 具体需求
* 本次只设计功能框架,具体存储哪些数据不在此次考虑范围内;
* 数据库使用SQLite;
* 使用ormpp库实现结构体到数据表的隐射,项目地址:https://github.com/qicosmos/ormpp;
* 数据记录功能在release版可以屏蔽,不做任何记录,避免交付版本的性能损失;
* 数据分类记录,所有数据存储到一个db文件中,不同的数据存储到不同的表中;
* 使用宏或其他方式,在代码中添加记录点,将指定数据写入数据库;
* 能够快速添加新的结构体数据,便于后续增加需要记录的数据;
* 作为一个调试功能,不必使用异步写入;
## 其他
* 如果有任何不明确或者有问题的地方,可以直接向我提问;
+25
View File
@@ -0,0 +1,25 @@
The MIT License (MIT)
Copyright (c) 2016 - present, Gabi Melman and spdlog contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-- NOTE: Third party dependency used by this software --
This software depends on the fmt lib (MIT License),
and users must comply to its license: https://raw.githubusercontent.com/fmtlib/fmt/master/LICENSE
+50
View File
@@ -0,0 +1,50 @@
# 第三方库来源说明(spdlog)
本目录按**上游原样内置** spdlog 源码(`include/` 头文件 + `src/` 编译单元,保留上游字节,含换行符,不做编码/格式转换),与 `third_party/ormpp`、`third_party/toml` 同类处理:仓库自带依赖,离线可配置、可交付。
| 内容 | 本目录位置 | 上游 | 版本 / 标识 |
|---|---|---|---|
| spdlog(C++ 日志库) | `include/spdlog/**`(105 个文件 / 940,660 字节)、`src/*.cpp`(7 个文件 / 9,168 字节) | <https://github.com/gabime/spdlog> | tag `v1.17.0`(commit `79524ddd08a4ec981b7fea76afd08ee05f83755d`,2026-01-04);`include/spdlog/version.h` = 1.17.0 |
| 内置 {fmt}(随 spdlog 内置,供格式化使用) | `include/spdlog/fmt/bundled/**`(23 个文件) | 随 spdlog 内置;独立仓库 <https://github.com/fmtlib/fmt> | {fmt} 12.1.0(spdlog v1.17.0 的 bundled 版本;未启用 `SPDLOG_FMT_EXTERNAL`) |
| 许可证 | `LICENSE` | — | MIT License(上游原文,1,323 字节) |
关键文件校验(与上游 v1.17.0 tarball 一致,可用下方复现步骤复核):
| 文件 | 字节数 | SHA-256 |
|---|---|---|
| `include/spdlog/version.h` | 417 | `a0c15d9295eba580732dada20940a443c53ddb47d8565c730d7af1ee5f3bd712` |
| `include/spdlog/spdlog.h` | 11,866 | `54283b3e3bf96c3d…`(前 16 位) |
| `include/spdlog/common.h` | 12,569 | `ddf4fa8ee4687f36…`(前 16 位) |
| `include/spdlog/fmt/bundled/format-inl.h` | 81,008 | `e7baf108b84e89b29915bb35733540f8f4262cd8f4b41c15754eed4d2f185f61` |
| `LICENSE` | 1,323 | `d678c20826e13925…`(前 16 位) |
> 说明:只保留使用所需的 `include/` 与 `src/`(上游 `tests/`、`bench/`、`example/`、`cmake/`、`scripts/`、`logos/`、`.github/` 等均不需要);`include/` + `src/` + `LICENSE` 共 **113 个文件 / 951,151 字节**(上游 v1.17.0 源码 tarball `spdlog-1.17.0.tar.gz` = 281,801 字节)。
## 复现步骤(重新获取/升级时执行)
```bat
:: 在临时目录执行(本机全局 core.autocrlf=true 会把文本转成 CRLF,必须显式关闭)
git -c core.autocrlf=false clone --depth 1 --branch v1.17.0 https://github.com/gabime/spdlog.git <tmp-spdlog>
:: 仅拷贝以下内容到本目录(其余目录不需要):
:: <tmp-spdlog>\include\ -> third_party\spdlog\include\
:: <tmp-spdlog>\src\ -> third_party\spdlog\src\
:: <tmp-spdlog>\LICENSE -> third_party\spdlog\LICENSE
```
或直接使用官方 tag 源码包(推荐,字节与上述 tarball 一致):
```bat
:: https://codeload.github.com/gabime/spdlog/tar.gz/refs/tags/v1.17.0
:: 解压后同样只拷贝 include\、src\、LICENSE
```
校验方式:`include/` 105 个文件 / 940,660 字节、`src/` 7 个文件 / 9,168 字节,且 `include/spdlog/version.h` 的 SHA-256 为 `a0c15d92…f3bd712`;本目录所有文件保持 **LF** 换行(上游字节,不转 CRLF/BOM)。
## 使用方式与约束
- **仅由日志模块 `data_process_class_dll/rdp_log.h`(经 `rdp_log.cpp`、各业务模块的 `RDP_LOG_*` 宏)使用**。
- 以**静态库**方式编译:根目录 `CMakeLists.txt` 定义目标 `spdlog`(`third_party/spdlog/src/*.cpp`,`SPDLOG_COMPILED_LIB`),链接进 `data_process_class_dll`;`rdp_record` 静态库(C++20,`rdp_record.cpp` 内也有日志)同样链接该目标。头文件包含路径与 `SPDLOG_COMPILED_LIB` / `SPDLOG_ACTIVE_LEVEL` 编译定义由该目标的 **PUBLIC** 接口传递给使用方。
- **编译期日志级别**:spdlog 的 `SPDLOG_ACTIVE_LEVEL` 由 CMake 选项 `RDP_LOG_LEVEL`(AUTO/TRACE/DEBUG/INFO/WARN/ERROR/CRITICAL/OFF)映射,AUTO 时 Debug = TRACE、其余 = INFO;低于该级别的 `RDP_LOG_*` 调用在编译期被剔除(实参不求值)。配置方式见 `README.md` 4.3、10.4。
- **告警处理**:第三方源码以目标级 `/w` 编译,本项目源码包含其头文件处用 `#pragma warning(push, 0)` / `(pop)` 包裹(`rdp_log.h`),维持 README 4.5 的 `/W3` 零告警约定;这是本目录作为第三方依赖的唯一例外。
- **升级方式**:整目录替换为新的上游快照,同步更新本文件的版本 / commit / 校验值,然后必须重新验证:4 个配置(Release/Debug × REC_OFF/REC_ON)构建通过(0 warning)+ 日志双写(标准输出 + `rdp_logs` 文件)、按日期+时间命名/同秒 `_1` 后缀、`RDP_LOG_LEVEL` 编译期过滤等运行期行为(见 README 10.4)。
+99
View File
@@ -0,0 +1,99 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
//
// Async logging using global thread pool
// All loggers created here share same global thread pool.
// Each log message is pushed to a queue along with a shared pointer to the
// logger.
// If a logger deleted while having pending messages in the queue, it's actual
// destruction will defer
// until all its messages are processed by the thread pool.
// This is because each message in the queue holds a shared_ptr to the
// originating logger.
#include <spdlog/async_logger.h>
#include <spdlog/details/registry.h>
#include <spdlog/details/thread_pool.h>
#include <functional>
#include <memory>
#include <mutex>
namespace spdlog {
namespace details {
static const size_t default_async_q_size = 8192;
}
// async logger factory - creates async loggers backed with thread pool.
// if a global thread pool doesn't already exist, create it with default queue
// size of 8192 items and single thread.
template <async_overflow_policy OverflowPolicy = async_overflow_policy::block>
struct async_factory_impl {
template <typename Sink, typename... SinkArgs>
static std::shared_ptr<async_logger> create(std::string logger_name, SinkArgs &&...args) {
auto &registry_inst = details::registry::instance();
// create global thread pool if not already exists..
auto &mutex = registry_inst.tp_mutex();
std::lock_guard<std::recursive_mutex> tp_lock(mutex);
auto tp = registry_inst.get_tp();
if (tp == nullptr) {
tp = std::make_shared<details::thread_pool>(details::default_async_q_size, 1U);
registry_inst.set_tp(tp);
}
auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...);
auto new_logger = std::make_shared<async_logger>(std::move(logger_name), std::move(sink),
std::move(tp), OverflowPolicy);
registry_inst.initialize_logger(new_logger);
return new_logger;
}
};
using async_factory = async_factory_impl<async_overflow_policy::block>;
using async_factory_nonblock = async_factory_impl<async_overflow_policy::overrun_oldest>;
template <typename Sink, typename... SinkArgs>
inline std::shared_ptr<spdlog::logger> create_async(std::string logger_name,
SinkArgs &&...sink_args) {
return async_factory::create<Sink>(std::move(logger_name),
std::forward<SinkArgs>(sink_args)...);
}
template <typename Sink, typename... SinkArgs>
inline std::shared_ptr<spdlog::logger> create_async_nb(std::string logger_name,
SinkArgs &&...sink_args) {
return async_factory_nonblock::create<Sink>(std::move(logger_name),
std::forward<SinkArgs>(sink_args)...);
}
// set global thread pool.
inline void init_thread_pool(size_t q_size,
size_t thread_count,
std::function<void()> on_thread_start,
std::function<void()> on_thread_stop) {
auto tp = std::make_shared<details::thread_pool>(q_size, thread_count, on_thread_start,
on_thread_stop);
details::registry::instance().set_tp(std::move(tp));
}
inline void init_thread_pool(size_t q_size,
size_t thread_count,
std::function<void()> on_thread_start) {
init_thread_pool(q_size, thread_count, on_thread_start, [] {});
}
inline void init_thread_pool(size_t q_size, size_t thread_count) {
init_thread_pool(q_size, thread_count, [] {}, [] {});
}
// get the global thread pool.
inline std::shared_ptr<spdlog::details::thread_pool> thread_pool() {
return details::registry::instance().get_tp();
}
} // namespace spdlog
+84
View File
@@ -0,0 +1,84 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#ifndef SPDLOG_HEADER_ONLY
#include <spdlog/async_logger.h>
#endif
#include <spdlog/details/thread_pool.h>
#include <spdlog/sinks/sink.h>
#include <memory>
#include <string>
SPDLOG_INLINE spdlog::async_logger::async_logger(std::string logger_name,
sinks_init_list sinks_list,
std::weak_ptr<details::thread_pool> tp,
async_overflow_policy overflow_policy)
: async_logger(std::move(logger_name),
sinks_list.begin(),
sinks_list.end(),
std::move(tp),
overflow_policy) {}
SPDLOG_INLINE spdlog::async_logger::async_logger(std::string logger_name,
sink_ptr single_sink,
std::weak_ptr<details::thread_pool> tp,
async_overflow_policy overflow_policy)
: async_logger(
std::move(logger_name), {std::move(single_sink)}, std::move(tp), overflow_policy) {}
// send the log message to the thread pool
SPDLOG_INLINE void spdlog::async_logger::sink_it_(const details::log_msg &msg){
SPDLOG_TRY{if (auto pool_ptr = thread_pool_.lock()){
pool_ptr -> post_log(shared_from_this(), msg, overflow_policy_);
}
else {
throw_spdlog_ex("async log: thread pool doesn't exist anymore");
}
}
SPDLOG_LOGGER_CATCH(msg.source)
}
// send flush request to the thread pool
SPDLOG_INLINE void spdlog::async_logger::flush_(){
SPDLOG_TRY{if (auto pool_ptr = thread_pool_.lock()){
pool_ptr -> post_flush(shared_from_this(), overflow_policy_);
}
else {
throw_spdlog_ex("async flush: thread pool doesn't exist anymore");
}
}
SPDLOG_LOGGER_CATCH(source_loc())
}
//
// backend functions - called from the thread pool to do the actual job
//
SPDLOG_INLINE void spdlog::async_logger::backend_sink_it_(const details::log_msg &msg) {
for (auto &sink : sinks_) {
if (sink->should_log(msg.level)) {
SPDLOG_TRY { sink->log(msg); }
SPDLOG_LOGGER_CATCH(msg.source)
}
}
if (should_flush_(msg)) {
backend_flush_();
}
}
SPDLOG_INLINE void spdlog::async_logger::backend_flush_() {
for (auto &sink : sinks_) {
SPDLOG_TRY { sink->flush(); }
SPDLOG_LOGGER_CATCH(source_loc())
}
}
SPDLOG_INLINE std::shared_ptr<spdlog::logger> spdlog::async_logger::clone(std::string new_name) {
auto cloned = std::make_shared<spdlog::async_logger>(*this);
cloned->name_ = std::move(new_name);
return cloned;
}
+74
View File
@@ -0,0 +1,74 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
// Fast asynchronous logger.
// Uses pre allocated queue.
// Creates a single back thread to pop messages from the queue and log them.
//
// Upon each log write the logger:
// 1. Checks if its log level is enough to log the message
// 2. Push a new copy of the message to a queue (or block the caller until
// space is available in the queue)
// Upon destruction, logs all remaining messages in the queue before
// destructing..
#include <spdlog/logger.h>
namespace spdlog {
// Async overflow policy - block by default.
enum class async_overflow_policy {
block, // Block until message can be enqueued
overrun_oldest, // Discard oldest message in the queue if full when trying to
// add new item.
discard_new // Discard new message if the queue is full when trying to add new item.
};
namespace details {
class thread_pool;
}
class SPDLOG_API async_logger final : public std::enable_shared_from_this<async_logger>,
public logger {
friend class details::thread_pool;
public:
template <typename It>
async_logger(std::string logger_name,
It begin,
It end,
std::weak_ptr<details::thread_pool> tp,
async_overflow_policy overflow_policy = async_overflow_policy::block)
: logger(std::move(logger_name), begin, end),
thread_pool_(std::move(tp)),
overflow_policy_(overflow_policy) {}
async_logger(std::string logger_name,
sinks_init_list sinks_list,
std::weak_ptr<details::thread_pool> tp,
async_overflow_policy overflow_policy = async_overflow_policy::block);
async_logger(std::string logger_name,
sink_ptr single_sink,
std::weak_ptr<details::thread_pool> tp,
async_overflow_policy overflow_policy = async_overflow_policy::block);
std::shared_ptr<logger> clone(std::string new_name) override;
protected:
void sink_it_(const details::log_msg &msg) override;
void flush_() override;
void backend_sink_it_(const details::log_msg &incoming_log_msg);
void backend_flush_();
private:
std::weak_ptr<details::thread_pool> thread_pool_;
async_overflow_policy overflow_policy_;
};
} // namespace spdlog
#ifdef SPDLOG_HEADER_ONLY
#include "async_logger-inl.h"
#endif
+40
View File
@@ -0,0 +1,40 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#include <spdlog/cfg/helpers.h>
#include <spdlog/details/registry.h>
//
// Init log levels using each argv entry that starts with "SPDLOG_LEVEL="
//
// set all loggers to debug level:
// example.exe "SPDLOG_LEVEL=debug"
// set logger1 to trace level
// example.exe "SPDLOG_LEVEL=logger1=trace"
// turn off all logging except for logger1 and logger2:
// example.exe "SPDLOG_LEVEL=off,logger1=debug,logger2=info"
namespace spdlog {
namespace cfg {
// search for SPDLOG_LEVEL= in the args and use it to init the levels
inline void load_argv_levels(int argc, const char **argv) {
const std::string spdlog_level_prefix = "SPDLOG_LEVEL=";
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
if (arg.find(spdlog_level_prefix) == 0) {
auto levels_string = arg.substr(spdlog_level_prefix.size());
helpers::load_levels(levels_string);
}
}
}
inline void load_argv_levels(int argc, char **argv) {
load_argv_levels(argc, const_cast<const char **>(argv));
}
} // namespace cfg
} // namespace spdlog
+36
View File
@@ -0,0 +1,36 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#include <spdlog/cfg/helpers.h>
#include <spdlog/details/os.h>
#include <spdlog/details/registry.h>
//
// Init levels and patterns from env variables SPDLOG_LEVEL
// Inspired from Rust's "env_logger" crate (https://crates.io/crates/env_logger).
// Note - fallback to "info" level on unrecognized levels
//
// Examples:
//
// set global level to debug:
// export SPDLOG_LEVEL=debug
//
// turn off all logging except for logger1:
// export SPDLOG_LEVEL="*=off,logger1=debug"
//
// turn off all logging except for logger1 and logger2:
// export SPDLOG_LEVEL="off,logger1=debug,logger2=info"
namespace spdlog {
namespace cfg {
inline void load_env_levels(const char* var = "SPDLOG_LEVEL") {
auto env_val = details::os::getenv(var);
if (!env_val.empty()) {
helpers::load_levels(env_val);
}
}
} // namespace cfg
} // namespace spdlog
+106
View File
@@ -0,0 +1,106 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#ifndef SPDLOG_HEADER_ONLY
#include <spdlog/cfg/helpers.h>
#endif
#include <spdlog/details/os.h>
#include <spdlog/details/registry.h>
#include <algorithm>
#include <sstream>
#include <string>
#include <utility>
namespace spdlog {
namespace cfg {
namespace helpers {
// inplace convert to lowercase
inline std::string &to_lower_(std::string &str) {
std::transform(str.begin(), str.end(), str.begin(), [](char ch) {
return static_cast<char>((ch >= 'A' && ch <= 'Z') ? ch + ('a' - 'A') : ch);
});
return str;
}
// inplace trim spaces
inline std::string &trim_(std::string &str) {
const char *spaces = " \n\r\t";
str.erase(str.find_last_not_of(spaces) + 1);
str.erase(0, str.find_first_not_of(spaces));
return str;
}
// return (name,value) trimmed pair from the given "name = value" string.
// return empty string on missing parts
// "key=val" => ("key", "val")
// " key = val " => ("key", "val")
// "key=" => ("key", "")
// "val" => ("", "val")
inline std::pair<std::string, std::string> extract_kv_(char sep, const std::string &str) {
auto n = str.find(sep);
std::string k, v;
if (n == std::string::npos) {
v = str;
} else {
k = str.substr(0, n);
v = str.substr(n + 1);
}
return std::make_pair(trim_(k), trim_(v));
}
// return vector of key/value pairs from a sequence of "K1=V1,K2=V2,.."
// "a=AAA,b=BBB,c=CCC,.." => {("a","AAA"),("b","BBB"),("c", "CCC"),...}
inline std::unordered_map<std::string, std::string> extract_key_vals_(const std::string &str) {
std::string token;
std::istringstream token_stream(str);
std::unordered_map<std::string, std::string> rv{};
while (std::getline(token_stream, token, ',')) {
if (token.empty()) {
continue;
}
auto kv = extract_kv_('=', token);
rv[kv.first] = kv.second;
}
return rv;
}
SPDLOG_INLINE void load_levels(const std::string &input) {
if (input.empty() || input.size() >= 32768) {
return;
}
auto key_vals = extract_key_vals_(input);
std::unordered_map<std::string, level::level_enum> levels;
level::level_enum global_level = level::info;
bool global_level_found = false;
for (auto &name_level : key_vals) {
const auto &logger_name = name_level.first;
const auto &level_name = to_lower_(name_level.second);
auto level = level::from_str(level_name);
// ignore unrecognized level names
if (level == level::off && level_name != "off") {
continue;
}
if (logger_name.empty()) // no logger name indicates global level
{
global_level_found = true;
global_level = level;
} else {
levels[logger_name] = level;
}
}
details::registry::instance().set_levels(std::move(levels),
global_level_found ? &global_level : nullptr);
}
} // namespace helpers
} // namespace cfg
} // namespace spdlog
+29
View File
@@ -0,0 +1,29 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#include <spdlog/common.h>
#include <unordered_map>
namespace spdlog {
namespace cfg {
namespace helpers {
//
// Init levels from given string
//
// Examples:
//
// set global level to debug: "debug"
// turn off all logging except for logger1: "off,logger1=debug"
// turn off all logging except for logger1 and logger2: "off,logger1=debug,logger2=info"
//
SPDLOG_API void load_levels(const std::string &txt);
} // namespace helpers
} // namespace cfg
} // namespace spdlog
#ifdef SPDLOG_HEADER_ONLY
#include "helpers-inl.h"
#endif // SPDLOG_HEADER_ONLY
+68
View File
@@ -0,0 +1,68 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#ifndef SPDLOG_HEADER_ONLY
#include <spdlog/common.h>
#endif
#include <algorithm>
#include <iterator>
namespace spdlog {
namespace level {
#if __cplusplus >= 201703L
constexpr
#endif
static string_view_t level_string_views[] SPDLOG_LEVEL_NAMES;
static const char *short_level_names[] SPDLOG_SHORT_LEVEL_NAMES;
SPDLOG_INLINE const string_view_t &to_string_view(spdlog::level::level_enum l) SPDLOG_NOEXCEPT {
return level_string_views[l];
}
SPDLOG_INLINE const char *to_short_c_str(spdlog::level::level_enum l) SPDLOG_NOEXCEPT {
return short_level_names[l];
}
SPDLOG_INLINE spdlog::level::level_enum from_str(const std::string &name) SPDLOG_NOEXCEPT {
auto it = std::find(std::begin(level_string_views), std::end(level_string_views), name);
if (it != std::end(level_string_views))
return static_cast<level::level_enum>(std::distance(std::begin(level_string_views), it));
// check also for "warn" and "err" before giving up..
if (name == "warn") {
return level::warn;
}
if (name == "err") {
return level::err;
}
return level::off;
}
} // namespace level
SPDLOG_INLINE spdlog_ex::spdlog_ex(std::string msg)
: msg_(std::move(msg)) {}
SPDLOG_INLINE spdlog_ex::spdlog_ex(const std::string &msg, int last_errno) {
#ifdef SPDLOG_USE_STD_FORMAT
msg_ = std::system_error(std::error_code(last_errno, std::generic_category()), msg).what();
#else
memory_buf_t outbuf;
fmt::format_system_error(outbuf, last_errno, msg.c_str());
msg_ = fmt::to_string(outbuf);
#endif
}
SPDLOG_INLINE const char *spdlog_ex::what() const SPDLOG_NOEXCEPT { return msg_.c_str(); }
SPDLOG_INLINE void throw_spdlog_ex(const std::string &msg, int last_errno) {
SPDLOG_THROW(spdlog_ex(msg, last_errno));
}
SPDLOG_INLINE void throw_spdlog_ex(std::string msg) { SPDLOG_THROW(spdlog_ex(std::move(msg))); }
} // namespace spdlog
+406
View File
@@ -0,0 +1,406 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#include <spdlog/details/null_mutex.h>
#include <spdlog/tweakme.h>
#include <atomic>
#include <chrono>
#include <cstdio>
#include <exception>
#include <functional>
#include <initializer_list>
#include <memory>
#include <string>
#include <type_traits>
#ifdef SPDLOG_USE_STD_FORMAT
#include <version>
#if __cpp_lib_format >= 202207L
#include <format>
#else
#include <string_view>
#endif
#endif
#ifdef SPDLOG_COMPILED_LIB
#undef SPDLOG_HEADER_ONLY
#if defined(SPDLOG_SHARED_LIB)
#if defined(_WIN32)
#ifdef spdlog_EXPORTS
#define SPDLOG_API __declspec(dllexport)
#else // !spdlog_EXPORTS
#define SPDLOG_API __declspec(dllimport)
#endif
#else // !defined(_WIN32)
#define SPDLOG_API __attribute__((visibility("default")))
#endif
#else // !defined(SPDLOG_SHARED_LIB)
#define SPDLOG_API
#endif
#define SPDLOG_INLINE
#else // !defined(SPDLOG_COMPILED_LIB)
#define SPDLOG_API
#define SPDLOG_HEADER_ONLY
#define SPDLOG_INLINE inline
#endif // #ifdef SPDLOG_COMPILED_LIB
#include <spdlog/fmt/fmt.h>
#if !defined(SPDLOG_USE_STD_FORMAT) && \
FMT_VERSION >= 80000 // backward compatibility with fmt versions older than 8
#define SPDLOG_FMT_RUNTIME(format_string) fmt::runtime(format_string)
#define SPDLOG_FMT_STRING(format_string) FMT_STRING(format_string)
#if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT)
#include <spdlog/fmt/xchar.h>
#endif
#else
#define SPDLOG_FMT_RUNTIME(format_string) format_string
#define SPDLOG_FMT_STRING(format_string) format_string
#endif
// visual studio up to 2013 does not support noexcept nor constexpr
#if defined(_MSC_VER) && (_MSC_VER < 1900)
#define SPDLOG_NOEXCEPT _NOEXCEPT
#define SPDLOG_CONSTEXPR
#else
#define SPDLOG_NOEXCEPT noexcept
#define SPDLOG_CONSTEXPR constexpr
#endif
// If building with std::format, can just use constexpr, otherwise if building with fmt
// SPDLOG_CONSTEXPR_FUNC needs to be set the same as FMT_CONSTEXPR to avoid situations where
// a constexpr function in spdlog could end up calling a non-constexpr function in fmt
// depending on the compiler
// If fmt determines it can't use constexpr, we should inline the function instead
#ifdef SPDLOG_USE_STD_FORMAT
#define SPDLOG_CONSTEXPR_FUNC constexpr
#else // Being built with fmt
#if FMT_USE_CONSTEXPR
#define SPDLOG_CONSTEXPR_FUNC FMT_CONSTEXPR
#else
#define SPDLOG_CONSTEXPR_FUNC inline
#endif
#endif
#if defined(__GNUC__) || defined(__clang__)
#define SPDLOG_DEPRECATED __attribute__((deprecated))
#elif defined(_MSC_VER)
#define SPDLOG_DEPRECATED __declspec(deprecated)
#else
#define SPDLOG_DEPRECATED
#endif
// disable thread local on msvc 2013
#ifndef SPDLOG_NO_TLS
#if (defined(_MSC_VER) && (_MSC_VER < 1900)) || defined(__cplusplus_winrt)
#define SPDLOG_NO_TLS 1
#endif
#endif
#ifndef SPDLOG_FUNCTION
#define SPDLOG_FUNCTION static_cast<const char *>(__FUNCTION__)
#endif
#ifdef SPDLOG_NO_EXCEPTIONS
#define SPDLOG_TRY
#define SPDLOG_THROW(ex) \
do { \
printf("spdlog fatal error: %s\n", ex.what()); \
std::abort(); \
} while (0)
#define SPDLOG_CATCH_STD
#else
#define SPDLOG_TRY try
#define SPDLOG_THROW(ex) throw(ex)
#define SPDLOG_CATCH_STD \
catch (const std::exception &) { \
}
#endif
namespace spdlog {
class formatter;
namespace sinks {
class sink;
}
#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES)
using filename_t = std::wstring;
// allow macro expansion to occur in SPDLOG_FILENAME_T
#define SPDLOG_FILENAME_T_INNER(s) L##s
#define SPDLOG_FILENAME_T(s) SPDLOG_FILENAME_T_INNER(s)
#else
using filename_t = std::string;
#define SPDLOG_FILENAME_T(s) s
#endif
using log_clock = std::chrono::system_clock;
using sink_ptr = std::shared_ptr<sinks::sink>;
using sinks_init_list = std::initializer_list<sink_ptr>;
using err_handler = std::function<void(const std::string &err_msg)>;
#ifdef SPDLOG_USE_STD_FORMAT
namespace fmt_lib = std;
using string_view_t = std::string_view;
using memory_buf_t = std::string;
template <typename... Args>
#if __cpp_lib_format >= 202207L
using format_string_t = std::format_string<Args...>;
#else
using format_string_t = std::string_view;
#endif
template <class T, class Char = char>
struct is_convertible_to_basic_format_string
: std::integral_constant<bool, std::is_convertible<T, std::basic_string_view<Char>>::value> {};
#if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT)
using wstring_view_t = std::wstring_view;
using wmemory_buf_t = std::wstring;
template <typename... Args>
#if __cpp_lib_format >= 202207L
using wformat_string_t = std::wformat_string<Args...>;
#else
using wformat_string_t = std::wstring_view;
#endif
#endif
#define SPDLOG_BUF_TO_STRING(x) x
#else // use fmt lib instead of std::format
namespace fmt_lib = fmt;
using string_view_t = fmt::basic_string_view<char>;
using memory_buf_t = fmt::basic_memory_buffer<char, 250>;
template <typename... Args>
using format_string_t = fmt::format_string<Args...>;
template <class T>
using remove_cvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
template <typename Char>
#if FMT_VERSION >= 90101
using fmt_runtime_string = fmt::runtime_format_string<Char>;
#else
using fmt_runtime_string = fmt::basic_runtime<Char>;
#endif
// clang doesn't like SFINAE disabled constructor in std::is_convertible<> so have to repeat the
// condition from basic_format_string here, in addition, fmt::basic_runtime<Char> is only
// convertible to basic_format_string<Char> but not basic_string_view<Char>
template <class T, class Char = char>
struct is_convertible_to_basic_format_string
: std::integral_constant<bool,
std::is_convertible<T, fmt::basic_string_view<Char>>::value ||
std::is_same<remove_cvref_t<T>, fmt_runtime_string<Char>>::value> {
};
#if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT)
using wstring_view_t = fmt::basic_string_view<wchar_t>;
using wmemory_buf_t = fmt::basic_memory_buffer<wchar_t, 250>;
template <typename... Args>
using wformat_string_t = fmt::wformat_string<Args...>;
#endif
#define SPDLOG_BUF_TO_STRING(x) fmt::to_string(x)
#endif
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
#ifndef _WIN32
#error SPDLOG_WCHAR_TO_UTF8_SUPPORT only supported on windows
#endif // _WIN32
#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT
template <class T>
struct is_convertible_to_any_format_string
: std::integral_constant<bool,
is_convertible_to_basic_format_string<T, char>::value ||
is_convertible_to_basic_format_string<T, wchar_t>::value> {};
#if defined(SPDLOG_NO_ATOMIC_LEVELS)
using level_t = details::null_atomic_int;
#else
using level_t = std::atomic<int>;
#endif
#define SPDLOG_LEVEL_TRACE 0
#define SPDLOG_LEVEL_DEBUG 1
#define SPDLOG_LEVEL_INFO 2
#define SPDLOG_LEVEL_WARN 3
#define SPDLOG_LEVEL_ERROR 4
#define SPDLOG_LEVEL_CRITICAL 5
#define SPDLOG_LEVEL_OFF 6
#if !defined(SPDLOG_ACTIVE_LEVEL)
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO
#endif
// Log level enum
namespace level {
enum level_enum : int {
trace = SPDLOG_LEVEL_TRACE,
debug = SPDLOG_LEVEL_DEBUG,
info = SPDLOG_LEVEL_INFO,
warn = SPDLOG_LEVEL_WARN,
err = SPDLOG_LEVEL_ERROR,
critical = SPDLOG_LEVEL_CRITICAL,
off = SPDLOG_LEVEL_OFF,
n_levels
};
#define SPDLOG_LEVEL_NAME_TRACE spdlog::string_view_t("trace", 5)
#define SPDLOG_LEVEL_NAME_DEBUG spdlog::string_view_t("debug", 5)
#define SPDLOG_LEVEL_NAME_INFO spdlog::string_view_t("info", 4)
#define SPDLOG_LEVEL_NAME_WARNING spdlog::string_view_t("warning", 7)
#define SPDLOG_LEVEL_NAME_ERROR spdlog::string_view_t("error", 5)
#define SPDLOG_LEVEL_NAME_CRITICAL spdlog::string_view_t("critical", 8)
#define SPDLOG_LEVEL_NAME_OFF spdlog::string_view_t("off", 3)
#if !defined(SPDLOG_LEVEL_NAMES)
#define SPDLOG_LEVEL_NAMES \
{ \
SPDLOG_LEVEL_NAME_TRACE, SPDLOG_LEVEL_NAME_DEBUG, SPDLOG_LEVEL_NAME_INFO, \
SPDLOG_LEVEL_NAME_WARNING, SPDLOG_LEVEL_NAME_ERROR, SPDLOG_LEVEL_NAME_CRITICAL, \
SPDLOG_LEVEL_NAME_OFF \
}
#endif
#if !defined(SPDLOG_SHORT_LEVEL_NAMES)
#define SPDLOG_SHORT_LEVEL_NAMES \
{ "T", "D", "I", "W", "E", "C", "O" }
#endif
SPDLOG_API const string_view_t &to_string_view(spdlog::level::level_enum l) SPDLOG_NOEXCEPT;
SPDLOG_API const char *to_short_c_str(spdlog::level::level_enum l) SPDLOG_NOEXCEPT;
SPDLOG_API spdlog::level::level_enum from_str(const std::string &name) SPDLOG_NOEXCEPT;
} // namespace level
//
// Color mode used by sinks with color support.
//
enum class color_mode { always, automatic, never };
//
// Pattern time - specific time getting to use for pattern_formatter.
// local time by default
//
enum class pattern_time_type {
local, // log localtime
utc // log utc
};
//
// Log exception
//
class SPDLOG_API spdlog_ex : public std::exception {
public:
explicit spdlog_ex(std::string msg);
spdlog_ex(const std::string &msg, int last_errno);
const char *what() const SPDLOG_NOEXCEPT override;
private:
std::string msg_;
};
[[noreturn]] SPDLOG_API void throw_spdlog_ex(const std::string &msg, int last_errno);
[[noreturn]] SPDLOG_API void throw_spdlog_ex(std::string msg);
struct source_loc {
SPDLOG_CONSTEXPR source_loc() = default;
SPDLOG_CONSTEXPR source_loc(const char *filename_in, int line_in, const char *funcname_in)
: filename{filename_in},
line{line_in},
funcname{funcname_in} {}
SPDLOG_CONSTEXPR bool empty() const SPDLOG_NOEXCEPT { return line <= 0; }
const char *filename{nullptr};
int line{0};
const char *funcname{nullptr};
};
struct file_event_handlers {
file_event_handlers()
: before_open(nullptr),
after_open(nullptr),
before_close(nullptr),
after_close(nullptr) {}
std::function<void(const filename_t &filename)> before_open;
std::function<void(const filename_t &filename, std::FILE *file_stream)> after_open;
std::function<void(const filename_t &filename, std::FILE *file_stream)> before_close;
std::function<void(const filename_t &filename)> after_close;
};
namespace details {
// to_string_view
SPDLOG_CONSTEXPR_FUNC spdlog::string_view_t to_string_view(const memory_buf_t &buf)
SPDLOG_NOEXCEPT {
return spdlog::string_view_t{buf.data(), buf.size()};
}
SPDLOG_CONSTEXPR_FUNC spdlog::string_view_t to_string_view(spdlog::string_view_t str)
SPDLOG_NOEXCEPT {
return str;
}
#if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT)
SPDLOG_CONSTEXPR_FUNC spdlog::wstring_view_t to_string_view(const wmemory_buf_t &buf)
SPDLOG_NOEXCEPT {
return spdlog::wstring_view_t{buf.data(), buf.size()};
}
SPDLOG_CONSTEXPR_FUNC spdlog::wstring_view_t to_string_view(spdlog::wstring_view_t str)
SPDLOG_NOEXCEPT {
return str;
}
#endif
#if defined(SPDLOG_USE_STD_FORMAT) && __cpp_lib_format >= 202207L
template <typename T, typename... Args>
SPDLOG_CONSTEXPR_FUNC std::basic_string_view<T> to_string_view(
std::basic_format_string<T, Args...> fmt) SPDLOG_NOEXCEPT {
return fmt.get();
}
#endif
// make_unique support for pre c++14
#if __cplusplus >= 201402L // C++14 and beyond
using std::enable_if_t;
using std::make_unique;
#else
template <bool B, class T = void>
using enable_if_t = typename std::enable_if<B, T>::type;
template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args &&...args) {
static_assert(!std::is_array<T>::value, "arrays not supported");
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
#endif
// to avoid useless casts (see https://github.com/nlohmann/json/issues/2893#issuecomment-889152324)
template <typename T, typename U, enable_if_t<!std::is_same<T, U>::value, int> = 0>
constexpr T conditional_static_cast(U value) {
return static_cast<T>(value);
}
template <typename T, typename U, enable_if_t<std::is_same<T, U>::value, int> = 0>
constexpr T conditional_static_cast(U value) {
return value;
}
} // namespace details
} // namespace spdlog
#ifdef SPDLOG_HEADER_ONLY
#include "common-inl.h"
#endif
@@ -0,0 +1,63 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#ifndef SPDLOG_HEADER_ONLY
#include <spdlog/details/backtracer.h>
#endif
namespace spdlog {
namespace details {
SPDLOG_INLINE backtracer::backtracer(const backtracer &other) {
std::lock_guard<std::mutex> lock(other.mutex_);
enabled_ = other.enabled();
messages_ = other.messages_;
}
SPDLOG_INLINE backtracer::backtracer(backtracer &&other) SPDLOG_NOEXCEPT {
std::lock_guard<std::mutex> lock(other.mutex_);
enabled_ = other.enabled();
messages_ = std::move(other.messages_);
}
SPDLOG_INLINE backtracer &backtracer::operator=(backtracer other) {
std::lock_guard<std::mutex> lock(mutex_);
enabled_ = other.enabled();
messages_ = std::move(other.messages_);
return *this;
}
SPDLOG_INLINE void backtracer::enable(size_t size) {
std::lock_guard<std::mutex> lock{mutex_};
enabled_.store(true, std::memory_order_relaxed);
messages_ = circular_q<log_msg_buffer>{size};
}
SPDLOG_INLINE void backtracer::disable() {
std::lock_guard<std::mutex> lock{mutex_};
enabled_.store(false, std::memory_order_relaxed);
}
SPDLOG_INLINE bool backtracer::enabled() const { return enabled_.load(std::memory_order_relaxed); }
SPDLOG_INLINE void backtracer::push_back(const log_msg &msg) {
std::lock_guard<std::mutex> lock{mutex_};
messages_.push_back(log_msg_buffer{msg});
}
SPDLOG_INLINE bool backtracer::empty() const {
std::lock_guard<std::mutex> lock{mutex_};
return messages_.empty();
}
// pop all items in the q and apply the given fun on each of them.
SPDLOG_INLINE void backtracer::foreach_pop(std::function<void(const details::log_msg &)> fun) {
std::lock_guard<std::mutex> lock{mutex_};
while (!messages_.empty()) {
auto &front_msg = messages_.front();
fun(front_msg);
messages_.pop_front();
}
}
} // namespace details
} // namespace spdlog
+45
View File
@@ -0,0 +1,45 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#include <spdlog/details/circular_q.h>
#include <spdlog/details/log_msg_buffer.h>
#include <atomic>
#include <functional>
#include <mutex>
// Store log messages in circular buffer.
// Useful for storing debug data in case of error/warning happens.
namespace spdlog {
namespace details {
class SPDLOG_API backtracer {
mutable std::mutex mutex_;
std::atomic<bool> enabled_{false};
circular_q<log_msg_buffer> messages_;
public:
backtracer() = default;
backtracer(const backtracer &other);
backtracer(backtracer &&other) SPDLOG_NOEXCEPT;
backtracer &operator=(backtracer other);
void enable(size_t size);
void disable();
bool enabled() const;
void push_back(const log_msg &msg);
bool empty() const;
// pop all items in the q and apply the given fun on each of them.
void foreach_pop(std::function<void(const details::log_msg &)> fun);
};
} // namespace details
} // namespace spdlog
#ifdef SPDLOG_HEADER_ONLY
#include "backtracer-inl.h"
#endif
+115
View File
@@ -0,0 +1,115 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
// circular q view of std::vector.
#pragma once
#include <cassert>
#include <vector>
#include "spdlog/common.h"
namespace spdlog {
namespace details {
template <typename T>
class circular_q {
size_t max_items_ = 0;
typename std::vector<T>::size_type head_ = 0;
typename std::vector<T>::size_type tail_ = 0;
size_t overrun_counter_ = 0;
std::vector<T> v_;
public:
using value_type = T;
// empty ctor - create a disabled queue with no elements allocated at all
circular_q() = default;
explicit circular_q(size_t max_items)
: max_items_(max_items + 1) // one item is reserved as marker for full q
,
v_(max_items_) {}
circular_q(const circular_q &) = default;
circular_q &operator=(const circular_q &) = default;
// move cannot be default,
// since we need to reset head_, tail_, etc to zero in the moved object
circular_q(circular_q &&other) SPDLOG_NOEXCEPT { copy_moveable(std::move(other)); }
circular_q &operator=(circular_q &&other) SPDLOG_NOEXCEPT {
copy_moveable(std::move(other));
return *this;
}
// push back, overrun (oldest) item if no room left
void push_back(T &&item) {
if (max_items_ > 0) {
v_[tail_] = std::move(item);
tail_ = (tail_ + 1) % max_items_;
if (tail_ == head_) // overrun last item if full
{
head_ = (head_ + 1) % max_items_;
++overrun_counter_;
}
}
}
// Return reference to the front item.
// If there are no elements in the container, the behavior is undefined.
const T &front() const { return v_[head_]; }
T &front() { return v_[head_]; }
// Return number of elements actually stored
size_t size() const {
if (tail_ >= head_) {
return tail_ - head_;
} else {
return max_items_ - (head_ - tail_);
}
}
// Return const reference to item by index.
// If index is out of range 0…size()-1, the behavior is undefined.
const T &at(size_t i) const {
assert(i < size());
return v_[(head_ + i) % max_items_];
}
// Pop item from front.
// If there are no elements in the container, the behavior is undefined.
void pop_front() { head_ = (head_ + 1) % max_items_; }
bool empty() const { return tail_ == head_; }
bool full() const {
// head is ahead of the tail by 1
if (max_items_ > 0) {
return ((tail_ + 1) % max_items_) == head_;
}
return false;
}
size_t overrun_counter() const { return overrun_counter_; }
void reset_overrun_counter() { overrun_counter_ = 0; }
private:
// copy from other&& and reset it to disabled state
void copy_moveable(circular_q &&other) SPDLOG_NOEXCEPT {
max_items_ = other.max_items_;
head_ = other.head_;
tail_ = other.tail_;
overrun_counter_ = other.overrun_counter_;
v_ = std::move(other.v_);
// put &&other in disabled, but valid state
other.max_items_ = 0;
other.head_ = other.tail_ = 0;
other.overrun_counter_ = 0;
}
};
} // namespace details
} // namespace spdlog
@@ -0,0 +1,28 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#include <mutex>
#include <spdlog/details/null_mutex.h>
namespace spdlog {
namespace details {
struct console_mutex {
using mutex_t = std::mutex;
static mutex_t &mutex() {
static mutex_t s_mutex;
return s_mutex;
}
};
struct console_nullmutex {
using mutex_t = null_mutex;
static mutex_t &mutex() {
static mutex_t s_mutex;
return s_mutex;
}
};
} // namespace details
} // namespace spdlog
@@ -0,0 +1,151 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#ifndef SPDLOG_HEADER_ONLY
#include <spdlog/details/file_helper.h>
#endif
#include <spdlog/common.h>
#include <spdlog/details/os.h>
#include <cerrno>
#include <cstdio>
#include <string>
#include <tuple>
namespace spdlog {
namespace details {
SPDLOG_INLINE file_helper::file_helper(const file_event_handlers &event_handlers)
: event_handlers_(event_handlers) {}
SPDLOG_INLINE file_helper::~file_helper() { close(); }
SPDLOG_INLINE void file_helper::open(const filename_t &fname, bool truncate) {
close();
filename_ = fname;
auto *mode = SPDLOG_FILENAME_T("ab");
auto *trunc_mode = SPDLOG_FILENAME_T("wb");
if (event_handlers_.before_open) {
event_handlers_.before_open(filename_);
}
for (int tries = 0; tries < open_tries_; ++tries) {
// create containing folder if not exists already.
os::create_dir(os::dir_name(fname));
if (truncate) {
// Truncate by opening-and-closing a tmp file in "wb" mode, always
// opening the actual log-we-write-to in "ab" mode, since that
// interacts more politely with eternal processes that might
// rotate/truncate the file underneath us.
std::FILE *tmp;
if (os::fopen_s(&tmp, fname, trunc_mode)) {
continue;
}
std::fclose(tmp);
}
if (!os::fopen_s(&fd_, fname, mode)) {
if (event_handlers_.after_open) {
event_handlers_.after_open(filename_, fd_);
}
return;
}
details::os::sleep_for_millis(open_interval_);
}
throw_spdlog_ex("Failed opening file " + os::filename_to_str(filename_) + " for writing",
errno);
}
SPDLOG_INLINE void file_helper::reopen(bool truncate) {
if (filename_.empty()) {
throw_spdlog_ex("Failed re opening file - was not opened before");
}
this->open(filename_, truncate);
}
SPDLOG_INLINE void file_helper::flush() {
if (std::fflush(fd_) != 0) {
throw_spdlog_ex("Failed flush to file " + os::filename_to_str(filename_), errno);
}
}
SPDLOG_INLINE void file_helper::sync() {
if (!os::fsync(fd_)) {
throw_spdlog_ex("Failed to fsync file " + os::filename_to_str(filename_), errno);
}
}
SPDLOG_INLINE void file_helper::close() {
if (fd_ != nullptr) {
if (event_handlers_.before_close) {
event_handlers_.before_close(filename_, fd_);
}
std::fclose(fd_);
fd_ = nullptr;
if (event_handlers_.after_close) {
event_handlers_.after_close(filename_);
}
}
}
SPDLOG_INLINE void file_helper::write(const memory_buf_t &buf) {
if (fd_ == nullptr) return;
size_t msg_size = buf.size();
auto data = buf.data();
if (!details::os::fwrite_bytes(data, msg_size, fd_)) {
throw_spdlog_ex("Failed writing to file " + os::filename_to_str(filename_), errno);
}
}
SPDLOG_INLINE size_t file_helper::size() const {
if (fd_ == nullptr) {
throw_spdlog_ex("Cannot use size() on closed file " + os::filename_to_str(filename_));
}
return os::filesize(fd_);
}
SPDLOG_INLINE const filename_t &file_helper::filename() const { return filename_; }
//
// return file path and its extension:
//
// "mylog.txt" => ("mylog", ".txt")
// "mylog" => ("mylog", "")
// "mylog." => ("mylog.", "")
// "/dir1/dir2/mylog.txt" => ("/dir1/dir2/mylog", ".txt")
//
// the starting dot in filenames is ignored (hidden files):
//
// ".mylog" => (".mylog". "")
// "my_folder/.mylog" => ("my_folder/.mylog", "")
// "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt")
SPDLOG_INLINE std::tuple<filename_t, filename_t> file_helper::split_by_extension(
const filename_t &fname) {
auto ext_index = fname.rfind('.');
// no valid extension found - return whole path and empty string as
// extension
if (ext_index == filename_t::npos || ext_index == 0 || ext_index == fname.size() - 1) {
return std::make_tuple(fname, filename_t());
}
// treat cases like "/etc/rc.d/somelogfile or "/abc/.hiddenfile"
auto folder_index = fname.find_last_of(details::os::folder_seps_filename);
if (folder_index != filename_t::npos && folder_index >= ext_index - 1) {
return std::make_tuple(fname, filename_t());
}
// finally - return a valid base and extension tuple
return std::make_tuple(fname.substr(0, ext_index), fname.substr(ext_index));
}
} // namespace details
} // namespace spdlog
+61
View File
@@ -0,0 +1,61 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#include <spdlog/common.h>
#include <tuple>
namespace spdlog {
namespace details {
// Helper class for file sinks.
// When failing to open a file, retry several times(5) with a delay interval(10 ms).
// Throw spdlog_ex exception on errors.
class SPDLOG_API file_helper {
public:
file_helper() = default;
explicit file_helper(const file_event_handlers &event_handlers);
file_helper(const file_helper &) = delete;
file_helper &operator=(const file_helper &) = delete;
~file_helper();
void open(const filename_t &fname, bool truncate = false);
void reopen(bool truncate);
void flush();
void sync();
void close();
void write(const memory_buf_t &buf);
size_t size() const;
const filename_t &filename() const;
//
// return file path and its extension:
//
// "mylog.txt" => ("mylog", ".txt")
// "mylog" => ("mylog", "")
// "mylog." => ("mylog.", "")
// "/dir1/dir2/mylog.txt" => ("/dir1/dir2/mylog", ".txt")
//
// the starting dot in filenames is ignored (hidden files):
//
// ".mylog" => (".mylog". "")
// "my_folder/.mylog" => ("my_folder/.mylog", "")
// "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt")
static std::tuple<filename_t, filename_t> split_by_extension(const filename_t &fname);
private:
const int open_tries_ = 5;
const unsigned int open_interval_ = 10;
std::FILE *fd_{nullptr};
filename_t filename_;
file_event_handlers event_handlers_;
};
} // namespace details
} // namespace spdlog
#ifdef SPDLOG_HEADER_ONLY
#include "file_helper-inl.h"
#endif
+141
View File
@@ -0,0 +1,141 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#include <chrono>
#include <iterator>
#include <spdlog/common.h>
#include <spdlog/fmt/fmt.h>
#include <type_traits>
#ifdef SPDLOG_USE_STD_FORMAT
#include <charconv>
#include <limits>
#endif
// Some fmt helpers to efficiently format and pad ints and strings
namespace spdlog {
namespace details {
namespace fmt_helper {
inline void append_string_view(spdlog::string_view_t view, memory_buf_t &dest) {
auto *buf_ptr = view.data();
dest.append(buf_ptr, buf_ptr + view.size());
}
#ifdef SPDLOG_USE_STD_FORMAT
template <typename T>
inline void append_int(T n, memory_buf_t &dest) {
// Buffer should be large enough to hold all digits (digits10 + 1) and a sign
SPDLOG_CONSTEXPR const auto BUF_SIZE = std::numeric_limits<T>::digits10 + 2;
char buf[BUF_SIZE];
auto [ptr, ec] = std::to_chars(buf, buf + BUF_SIZE, n, 10);
if (ec == std::errc()) {
dest.append(buf, ptr);
} else {
throw_spdlog_ex("Failed to format int", static_cast<int>(ec));
}
}
#else
template <typename T>
inline void append_int(T n, memory_buf_t &dest) {
fmt::format_int i(n);
dest.append(i.data(), i.data() + i.size());
}
#endif
template <typename T>
SPDLOG_CONSTEXPR_FUNC unsigned int count_digits_fallback(T n) {
// taken from fmt: https://github.com/fmtlib/fmt/blob/8.0.1/include/fmt/format.h#L899-L912
unsigned int count = 1;
for (;;) {
// Integer division is slow so do it for a group of four digits instead
// of for every digit. The idea comes from the talk by Alexandrescu
// "Three Optimization Tips for C++". See speed-test for a comparison.
if (n < 10) return count;
if (n < 100) return count + 1;
if (n < 1000) return count + 2;
if (n < 10000) return count + 3;
n /= 10000u;
count += 4;
}
}
template <typename T>
inline unsigned int count_digits(T n) {
using count_type =
typename std::conditional<(sizeof(T) > sizeof(uint32_t)), uint64_t, uint32_t>::type;
#ifdef SPDLOG_USE_STD_FORMAT
return count_digits_fallback(static_cast<count_type>(n));
#else
return static_cast<unsigned int>(fmt::
// fmt 7.0.0 renamed the internal namespace to detail.
// See: https://github.com/fmtlib/fmt/issues/1538
#if FMT_VERSION < 70000
internal
#else
detail
#endif
::count_digits(static_cast<count_type>(n)));
#endif
}
inline void pad2(int n, memory_buf_t &dest) {
if (n >= 0 && n < 100) // 0-99
{
dest.push_back(static_cast<char>('0' + n / 10));
dest.push_back(static_cast<char>('0' + n % 10));
} else // unlikely, but just in case, let fmt deal with it
{
fmt_lib::format_to(std::back_inserter(dest), SPDLOG_FMT_STRING("{:02}"), n);
}
}
template <typename T>
inline void pad_uint(T n, unsigned int width, memory_buf_t &dest) {
static_assert(std::is_unsigned<T>::value, "pad_uint must get unsigned T");
for (auto digits = count_digits(n); digits < width; digits++) {
dest.push_back('0');
}
append_int(n, dest);
}
template <typename T>
inline void pad3(T n, memory_buf_t &dest) {
static_assert(std::is_unsigned<T>::value, "pad3 must get unsigned T");
if (n < 1000) {
dest.push_back(static_cast<char>(n / 100 + '0'));
n = n % 100;
dest.push_back(static_cast<char>((n / 10) + '0'));
dest.push_back(static_cast<char>((n % 10) + '0'));
} else {
append_int(n, dest);
}
}
template <typename T>
inline void pad6(T n, memory_buf_t &dest) {
pad_uint(n, 6, dest);
}
template <typename T>
inline void pad9(T n, memory_buf_t &dest) {
pad_uint(n, 9, dest);
}
// return fraction of a second of the given time_point.
// e.g.
// fraction<std::milliseconds>(tp) -> will return the millis part of the second
template <typename ToDuration>
inline ToDuration time_fraction(log_clock::time_point tp) {
using std::chrono::duration_cast;
using std::chrono::seconds;
auto duration = tp.time_since_epoch();
auto secs = duration_cast<seconds>(duration);
return duration_cast<ToDuration>(duration) - duration_cast<ToDuration>(secs);
}
} // namespace fmt_helper
} // namespace details
} // namespace spdlog
+44
View File
@@ -0,0 +1,44 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#ifndef SPDLOG_HEADER_ONLY
#include <spdlog/details/log_msg.h>
#endif
#include <spdlog/details/os.h>
namespace spdlog {
namespace details {
SPDLOG_INLINE log_msg::log_msg(spdlog::log_clock::time_point log_time,
spdlog::source_loc loc,
string_view_t a_logger_name,
spdlog::level::level_enum lvl,
spdlog::string_view_t msg)
: logger_name(a_logger_name),
level(lvl),
time(log_time)
#ifndef SPDLOG_NO_THREAD_ID
,
thread_id(os::thread_id())
#endif
,
source(loc),
payload(msg) {
}
SPDLOG_INLINE log_msg::log_msg(spdlog::source_loc loc,
string_view_t a_logger_name,
spdlog::level::level_enum lvl,
spdlog::string_view_t msg)
: log_msg(os::now(), loc, a_logger_name, lvl, msg) {}
SPDLOG_INLINE log_msg::log_msg(string_view_t a_logger_name,
spdlog::level::level_enum lvl,
spdlog::string_view_t msg)
: log_msg(os::now(), source_loc{}, a_logger_name, lvl, msg) {}
} // namespace details
} // namespace spdlog
+40
View File
@@ -0,0 +1,40 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#include <spdlog/common.h>
#include <string>
namespace spdlog {
namespace details {
struct SPDLOG_API log_msg {
log_msg() = default;
log_msg(log_clock::time_point log_time,
source_loc loc,
string_view_t logger_name,
level::level_enum lvl,
string_view_t msg);
log_msg(source_loc loc, string_view_t logger_name, level::level_enum lvl, string_view_t msg);
log_msg(string_view_t logger_name, level::level_enum lvl, string_view_t msg);
log_msg(const log_msg &other) = default;
log_msg &operator=(const log_msg &other) = default;
string_view_t logger_name;
level::level_enum level{level::off};
log_clock::time_point time;
size_t thread_id{0};
// wrapping the formatted text with color (updated by pattern_formatter).
mutable size_t color_range_start{0};
mutable size_t color_range_end{0};
source_loc source;
string_view_t payload;
};
} // namespace details
} // namespace spdlog
#ifdef SPDLOG_HEADER_ONLY
#include "log_msg-inl.h"
#endif
@@ -0,0 +1,54 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#ifndef SPDLOG_HEADER_ONLY
#include <spdlog/details/log_msg_buffer.h>
#endif
namespace spdlog {
namespace details {
SPDLOG_INLINE log_msg_buffer::log_msg_buffer(const log_msg &orig_msg)
: log_msg{orig_msg} {
buffer.append(logger_name.begin(), logger_name.end());
buffer.append(payload.begin(), payload.end());
update_string_views();
}
SPDLOG_INLINE log_msg_buffer::log_msg_buffer(const log_msg_buffer &other)
: log_msg{other} {
buffer.append(logger_name.begin(), logger_name.end());
buffer.append(payload.begin(), payload.end());
update_string_views();
}
SPDLOG_INLINE log_msg_buffer::log_msg_buffer(log_msg_buffer &&other) SPDLOG_NOEXCEPT
: log_msg{other},
buffer{std::move(other.buffer)} {
update_string_views();
}
SPDLOG_INLINE log_msg_buffer &log_msg_buffer::operator=(const log_msg_buffer &other) {
log_msg::operator=(other);
buffer.clear();
buffer.append(other.buffer.data(), other.buffer.data() + other.buffer.size());
update_string_views();
return *this;
}
SPDLOG_INLINE log_msg_buffer &log_msg_buffer::operator=(log_msg_buffer &&other) SPDLOG_NOEXCEPT {
log_msg::operator=(other);
buffer = std::move(other.buffer);
update_string_views();
return *this;
}
SPDLOG_INLINE void log_msg_buffer::update_string_views() {
logger_name = string_view_t{buffer.data(), logger_name.size()};
payload = string_view_t{buffer.data() + logger_name.size(), payload.size()};
}
} // namespace details
} // namespace spdlog
@@ -0,0 +1,32 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#include <spdlog/details/log_msg.h>
namespace spdlog {
namespace details {
// Extend log_msg with internal buffer to store its payload.
// This is needed since log_msg holds string_views that points to stack data.
class SPDLOG_API log_msg_buffer : public log_msg {
memory_buf_t buffer;
void update_string_views();
public:
log_msg_buffer() = default;
explicit log_msg_buffer(const log_msg &orig_msg);
log_msg_buffer(const log_msg_buffer &other);
log_msg_buffer(log_msg_buffer &&other) SPDLOG_NOEXCEPT;
log_msg_buffer &operator=(const log_msg_buffer &other);
log_msg_buffer &operator=(log_msg_buffer &&other) SPDLOG_NOEXCEPT;
};
} // namespace details
} // namespace spdlog
#ifdef SPDLOG_HEADER_ONLY
#include "log_msg_buffer-inl.h"
#endif
@@ -0,0 +1,177 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
// multi producer-multi consumer blocking queue.
// enqueue(..) - will block until room found to put the new message.
// enqueue_nowait(..) - enqueue immediately. overruns oldest message if no
// room left.
// dequeue_for(..) - will block until the queue is not empty or timeout have
// passed.
#include <spdlog/details/circular_q.h>
#include <atomic>
#include <condition_variable>
#include <mutex>
namespace spdlog {
namespace details {
template <typename T>
class mpmc_blocking_queue {
public:
using item_type = T;
explicit mpmc_blocking_queue(size_t max_items)
: q_(max_items) {}
#ifndef __MINGW32__
// try to enqueue and block if no room left
void enqueue(T &&item) {
{
std::unique_lock<std::mutex> lock(queue_mutex_);
pop_cv_.wait(lock, [this] { return !this->q_.full(); });
q_.push_back(std::move(item));
}
push_cv_.notify_one();
}
// enqueue immediately. overrun oldest message in the queue if no room left.
void enqueue_nowait(T &&item) {
{
std::unique_lock<std::mutex> lock(queue_mutex_);
q_.push_back(std::move(item));
}
push_cv_.notify_one();
}
void enqueue_if_have_room(T &&item) {
bool pushed = false;
{
std::unique_lock<std::mutex> lock(queue_mutex_);
if (!q_.full()) {
q_.push_back(std::move(item));
pushed = true;
}
}
if (pushed) {
push_cv_.notify_one();
} else {
++discard_counter_;
}
}
// dequeue with a timeout.
// Return true, if succeeded dequeue item, false otherwise
bool dequeue_for(T &popped_item, std::chrono::milliseconds wait_duration) {
{
std::unique_lock<std::mutex> lock(queue_mutex_);
if (!push_cv_.wait_for(lock, wait_duration, [this] { return !this->q_.empty(); })) {
return false;
}
popped_item = std::move(q_.front());
q_.pop_front();
}
pop_cv_.notify_one();
return true;
}
// blocking dequeue without a timeout.
void dequeue(T &popped_item) {
{
std::unique_lock<std::mutex> lock(queue_mutex_);
push_cv_.wait(lock, [this] { return !this->q_.empty(); });
popped_item = std::move(q_.front());
q_.pop_front();
}
pop_cv_.notify_one();
}
#else
// apparently mingw deadlocks if the mutex is released before cv.notify_one(),
// so release the mutex at the very end each function.
// try to enqueue and block if no room left
void enqueue(T &&item) {
std::unique_lock<std::mutex> lock(queue_mutex_);
pop_cv_.wait(lock, [this] { return !this->q_.full(); });
q_.push_back(std::move(item));
push_cv_.notify_one();
}
// enqueue immediately. overrun oldest message in the queue if no room left.
void enqueue_nowait(T &&item) {
std::unique_lock<std::mutex> lock(queue_mutex_);
q_.push_back(std::move(item));
push_cv_.notify_one();
}
void enqueue_if_have_room(T &&item) {
bool pushed = false;
std::unique_lock<std::mutex> lock(queue_mutex_);
if (!q_.full()) {
q_.push_back(std::move(item));
pushed = true;
}
if (pushed) {
push_cv_.notify_one();
} else {
++discard_counter_;
}
}
// dequeue with a timeout.
// Return true, if succeeded dequeue item, false otherwise
bool dequeue_for(T &popped_item, std::chrono::milliseconds wait_duration) {
std::unique_lock<std::mutex> lock(queue_mutex_);
if (!push_cv_.wait_for(lock, wait_duration, [this] { return !this->q_.empty(); })) {
return false;
}
popped_item = std::move(q_.front());
q_.pop_front();
pop_cv_.notify_one();
return true;
}
// blocking dequeue without a timeout.
void dequeue(T &popped_item) {
std::unique_lock<std::mutex> lock(queue_mutex_);
push_cv_.wait(lock, [this] { return !this->q_.empty(); });
popped_item = std::move(q_.front());
q_.pop_front();
pop_cv_.notify_one();
}
#endif
size_t overrun_counter() {
std::lock_guard<std::mutex> lock(queue_mutex_);
return q_.overrun_counter();
}
size_t discard_counter() { return discard_counter_.load(std::memory_order_relaxed); }
size_t size() {
std::lock_guard<std::mutex> lock(queue_mutex_);
return q_.size();
}
void reset_overrun_counter() {
std::lock_guard<std::mutex> lock(queue_mutex_);
q_.reset_overrun_counter();
}
void reset_discard_counter() { discard_counter_.store(0, std::memory_order_relaxed); }
private:
std::mutex queue_mutex_;
std::condition_variable push_cv_;
std::condition_variable pop_cv_;
spdlog::details::circular_q<T> q_;
std::atomic<size_t> discard_counter_{0};
};
} // namespace details
} // namespace spdlog
+35
View File
@@ -0,0 +1,35 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#include <atomic>
#include <utility>
// null, no cost dummy "mutex" and dummy "atomic" int
namespace spdlog {
namespace details {
struct null_mutex {
void lock() const {}
void unlock() const {}
};
struct null_atomic_int {
int value;
null_atomic_int() = default;
explicit null_atomic_int(int new_value)
: value(new_value) {}
int load(std::memory_order = std::memory_order_relaxed) const { return value; }
void store(int new_value, std::memory_order = std::memory_order_relaxed) { value = new_value; }
int exchange(int new_value, std::memory_order = std::memory_order_relaxed) {
std::swap(new_value, value);
return new_value; // return value before the call
}
};
} // namespace details
} // namespace spdlog
+570
View File
@@ -0,0 +1,570 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#ifndef SPDLOG_HEADER_ONLY
#include <spdlog/details/os.h>
#endif
#include <spdlog/common.h>
#include <algorithm>
#include <array>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <thread>
#ifdef _WIN32
#include <spdlog/details/windows_include.h>
#include <io.h> // for _get_osfhandle, _isatty, _fileno
#include <process.h> // for _get_pid
#ifdef __MINGW32__
#include <share.h>
#endif
#if defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)
#include <cassert>
#include <limits>
#endif
#include <direct.h> // for _mkdir/_wmkdir
#else // unix
#include <fcntl.h>
#include <unistd.h>
#ifdef __linux__
#include <sys/syscall.h> //Use gettid() syscall under linux to get thread id
#elif defined(_AIX)
#include <pthread.h> // for pthread_getthrds_np
#elif defined(__DragonFly__) || defined(__FreeBSD__)
#include <pthread_np.h> // for pthread_getthreadid_np
#elif defined(__NetBSD__)
#include <lwp.h> // for _lwp_self
#elif defined(__sun)
#include <thread.h> // for thr_self
#endif
#endif // unix
#if defined __APPLE__
#include <AvailabilityMacros.h>
#endif
#ifndef __has_feature // Clang - feature checking macros.
#define __has_feature(x) 0 // Compatibility with non-clang compilers.
#endif
namespace spdlog {
namespace details {
namespace os {
SPDLOG_INLINE spdlog::log_clock::time_point now() SPDLOG_NOEXCEPT {
#if defined __linux__ && defined SPDLOG_CLOCK_COARSE
timespec ts;
::clock_gettime(CLOCK_REALTIME_COARSE, &ts);
return std::chrono::time_point<log_clock, typename log_clock::duration>(
std::chrono::duration_cast<typename log_clock::duration>(
std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec)));
#else
return log_clock::now();
#endif
}
SPDLOG_INLINE std::tm localtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT {
#ifdef _WIN32
std::tm tm;
::localtime_s(&tm, &time_tt);
#else
std::tm tm;
::localtime_r(&time_tt, &tm);
#endif
return tm;
}
SPDLOG_INLINE std::tm localtime() SPDLOG_NOEXCEPT {
std::time_t now_t = ::time(nullptr);
return localtime(now_t);
}
SPDLOG_INLINE std::tm gmtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT {
#ifdef _WIN32
std::tm tm;
::gmtime_s(&tm, &time_tt);
#else
std::tm tm;
::gmtime_r(&time_tt, &tm);
#endif
return tm;
}
SPDLOG_INLINE std::tm gmtime() SPDLOG_NOEXCEPT {
std::time_t now_t = ::time(nullptr);
return gmtime(now_t);
}
// fopen_s on non windows for writing
SPDLOG_INLINE bool fopen_s(FILE **fp, const filename_t &filename, const filename_t &mode) {
#ifdef _WIN32
#ifdef SPDLOG_WCHAR_FILENAMES
*fp = ::_wfsopen((filename.c_str()), mode.c_str(), _SH_DENYNO);
#else
*fp = ::_fsopen((filename.c_str()), mode.c_str(), _SH_DENYNO);
#endif
#if defined(SPDLOG_PREVENT_CHILD_FD)
if (*fp != nullptr) {
auto file_handle = reinterpret_cast<HANDLE>(_get_osfhandle(::_fileno(*fp)));
if (!::SetHandleInformation(file_handle, HANDLE_FLAG_INHERIT, 0)) {
::fclose(*fp);
*fp = nullptr;
}
}
#endif
#else // unix
#if defined(SPDLOG_PREVENT_CHILD_FD)
const int mode_flag = mode == SPDLOG_FILENAME_T("ab") ? O_APPEND : O_TRUNC;
const int fd =
::open((filename.c_str()), O_CREAT | O_WRONLY | O_CLOEXEC | mode_flag, mode_t(0644));
if (fd == -1) {
return true;
}
*fp = ::fdopen(fd, mode.c_str());
if (*fp == nullptr) {
::close(fd);
}
#else
*fp = ::fopen((filename.c_str()), mode.c_str());
#endif
#endif
return *fp == nullptr;
}
SPDLOG_INLINE int remove(const filename_t &filename) SPDLOG_NOEXCEPT {
#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES)
return ::_wremove(filename.c_str());
#else
return std::remove(filename.c_str());
#endif
}
SPDLOG_INLINE int remove_if_exists(const filename_t &filename) SPDLOG_NOEXCEPT {
return path_exists(filename) ? remove(filename) : 0;
}
SPDLOG_INLINE int rename(const filename_t &filename1, const filename_t &filename2) SPDLOG_NOEXCEPT {
#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES)
return ::_wrename(filename1.c_str(), filename2.c_str());
#else
return std::rename(filename1.c_str(), filename2.c_str());
#endif
}
// Return true if path exists (file or directory)
SPDLOG_INLINE bool path_exists(const filename_t &filename) SPDLOG_NOEXCEPT {
#ifdef _WIN32
struct _stat buffer;
#ifdef SPDLOG_WCHAR_FILENAMES
return (::_wstat(filename.c_str(), &buffer) == 0);
#else
return (::_stat(filename.c_str(), &buffer) == 0);
#endif
#else // common linux/unix all have the stat system call
struct stat buffer;
return (::stat(filename.c_str(), &buffer) == 0);
#endif
}
#ifdef _MSC_VER
// avoid warning about unreachable statement at the end of filesize()
#pragma warning(push)
#pragma warning(disable : 4702)
#endif
// Return file size according to open FILE* object
SPDLOG_INLINE size_t filesize(FILE *f) {
if (f == nullptr) {
throw_spdlog_ex("Failed getting file size. fd is null");
}
#if defined(_WIN32) && !defined(__CYGWIN__)
int fd = ::_fileno(f);
#if defined(_WIN64) // 64 bits
__int64 ret = ::_filelengthi64(fd);
if (ret >= 0) {
return static_cast<size_t>(ret);
}
#else // windows 32 bits
long ret = ::_filelength(fd);
if (ret >= 0) {
return static_cast<size_t>(ret);
}
#endif
#else // unix
// OpenBSD and AIX doesn't compile with :: before the fileno(..)
#if defined(__OpenBSD__) || defined(_AIX)
int fd = fileno(f);
#else
int fd = ::fileno(f);
#endif
// 64 bits(but not in osx, linux/musl or cygwin, where fstat64 is deprecated)
#if ((defined(__linux__) && defined(__GLIBC__)) || defined(__sun) || defined(_AIX)) && \
(defined(__LP64__) || defined(_LP64))
struct stat64 st;
if (::fstat64(fd, &st) == 0) {
return static_cast<size_t>(st.st_size);
}
#else // other unix or linux 32 bits or cygwin
struct stat st;
if (::fstat(fd, &st) == 0) {
return static_cast<size_t>(st.st_size);
}
#endif
#endif
throw_spdlog_ex("Failed getting file size from fd", errno);
return 0; // will not be reached.
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#if !defined(SPDLOG_NO_TZ_OFFSET)
#ifdef _WIN32
// Compare the timestamp as Local (mktime) vs UTC (_mkgmtime) to get the offset.
SPDLOG_INLINE int utc_minutes_offset(const std::tm &tm) {
std::tm local_tm = tm; // copy since mktime might adjust it (normalize dates, set tm_isdst)
std::time_t local_time_t = std::mktime(&local_tm);
if (local_time_t == -1) {
return 0; // fallback
}
std::time_t utc_time_t = _mkgmtime(&local_tm);
if (utc_time_t == -1) {
return 0; // fallback
}
auto offset_seconds = utc_time_t - local_time_t;
return static_cast<int>(offset_seconds / 60);
}
#else
// On unix simply use tm_gmtoff
SPDLOG_INLINE int utc_minutes_offset(const std::tm &tm) {
return static_cast<int>(tm.tm_gmtoff / 60);
}
#endif // _WIN32
#endif // SPDLOG_NO_TZ_OFFSET
// Return current thread id as size_t
// It exists because the std::this_thread::get_id() is much slower(especially
// under VS 2013)
SPDLOG_INLINE size_t _thread_id() SPDLOG_NOEXCEPT {
#ifdef _WIN32
return static_cast<size_t>(::GetCurrentThreadId());
#elif defined(__linux__)
#if defined(__ANDROID__) && defined(__ANDROID_API__) && (__ANDROID_API__ < 21)
#define SYS_gettid __NR_gettid
#endif
return static_cast<size_t>(::syscall(SYS_gettid));
#elif defined(_AIX)
struct __pthrdsinfo buf;
int reg_size = 0;
pthread_t pt = pthread_self();
int retval = pthread_getthrds_np(&pt, PTHRDSINFO_QUERY_TID, &buf, sizeof(buf), NULL, &reg_size);
int tid = (!retval) ? buf.__pi_tid : 0;
return static_cast<size_t>(tid);
#elif defined(__DragonFly__) || defined(__FreeBSD__)
return static_cast<size_t>(::pthread_getthreadid_np());
#elif defined(__NetBSD__)
return static_cast<size_t>(::_lwp_self());
#elif defined(__OpenBSD__)
return static_cast<size_t>(::getthrid());
#elif defined(__sun)
return static_cast<size_t>(::thr_self());
#elif __APPLE__
uint64_t tid;
// There is no pthread_threadid_np prior to Mac OS X 10.6, and it is not supported on any PPC,
// including 10.6.8 Rosetta. __POWERPC__ is Apple-specific define encompassing ppc and ppc64.
#ifdef MAC_OS_X_VERSION_MAX_ALLOWED
{
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 1060) || defined(__POWERPC__)
tid = pthread_mach_thread_np(pthread_self());
#elif MAC_OS_X_VERSION_MIN_REQUIRED < 1060
if (&pthread_threadid_np) {
pthread_threadid_np(nullptr, &tid);
} else {
tid = pthread_mach_thread_np(pthread_self());
}
#else
pthread_threadid_np(nullptr, &tid);
#endif
}
#else
pthread_threadid_np(nullptr, &tid);
#endif
return static_cast<size_t>(tid);
#else // Default to standard C++11 (other Unix)
return static_cast<size_t>(std::hash<std::thread::id>()(std::this_thread::get_id()));
#endif
}
// Return current thread id as size_t (from thread local storage)
SPDLOG_INLINE size_t thread_id() SPDLOG_NOEXCEPT {
#if defined(SPDLOG_NO_TLS)
return _thread_id();
#else // cache thread id in tls
static thread_local const size_t tid = _thread_id();
return tid;
#endif
}
// This is avoid msvc issue in sleep_for that happens if the clock changes.
// See https://github.com/gabime/spdlog/issues/609
SPDLOG_INLINE void sleep_for_millis(unsigned int milliseconds) SPDLOG_NOEXCEPT {
#if defined(_WIN32)
::Sleep(milliseconds);
#else
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
#endif
}
// wchar support for windows file names (SPDLOG_WCHAR_FILENAMES must be defined)
#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES)
SPDLOG_INLINE std::string filename_to_str(const filename_t &filename) {
memory_buf_t buf;
wstr_to_utf8buf(filename, buf);
return SPDLOG_BUF_TO_STRING(buf);
}
#else
SPDLOG_INLINE std::string filename_to_str(const filename_t &filename) { return filename; }
#endif
SPDLOG_INLINE int pid() SPDLOG_NOEXCEPT {
#ifdef _WIN32
return conditional_static_cast<int>(::GetCurrentProcessId());
#else
return conditional_static_cast<int>(::getpid());
#endif
}
// Determine if the terminal supports colors
// Based on: https://github.com/agauniyal/rang/
SPDLOG_INLINE bool is_color_terminal() SPDLOG_NOEXCEPT {
#ifdef _WIN32
return true;
#else
static const bool result = []() {
const char *env_colorterm_p = std::getenv("COLORTERM");
if (env_colorterm_p != nullptr) {
return true;
}
static constexpr std::array<const char *, 16> terms = {
{"ansi", "color", "console", "cygwin", "gnome", "konsole", "kterm", "linux", "msys",
"putty", "rxvt", "screen", "vt100", "xterm", "alacritty", "vt102"}};
const char *env_term_p = std::getenv("TERM");
if (env_term_p == nullptr) {
return false;
}
return std::any_of(terms.begin(), terms.end(), [&](const char *term) {
return std::strstr(env_term_p, term) != nullptr;
});
}();
return result;
#endif
}
// Determine if the terminal attached
// Source: https://github.com/agauniyal/rang/
SPDLOG_INLINE bool in_terminal(FILE *file) SPDLOG_NOEXCEPT {
#ifdef _WIN32
return ::_isatty(_fileno(file)) != 0;
#else
return ::isatty(fileno(file)) != 0;
#endif
}
#if (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32)
SPDLOG_INLINE void wstr_to_utf8buf(wstring_view_t wstr, memory_buf_t &target) {
if (wstr.size() > static_cast<size_t>((std::numeric_limits<int>::max)()) / 4 - 1) {
throw_spdlog_ex("UTF-16 string is too big to be converted to UTF-8");
}
int wstr_size = static_cast<int>(wstr.size());
if (wstr_size == 0) {
target.resize(0);
return;
}
int result_size = static_cast<int>(target.capacity());
if ((wstr_size + 1) * 4 > result_size) {
result_size =
::WideCharToMultiByte(CP_UTF8, 0, wstr.data(), wstr_size, NULL, 0, NULL, NULL);
}
if (result_size > 0) {
target.resize(result_size);
result_size = ::WideCharToMultiByte(CP_UTF8, 0, wstr.data(), wstr_size, target.data(),
result_size, NULL, NULL);
if (result_size > 0) {
target.resize(result_size);
return;
}
}
throw_spdlog_ex(
fmt_lib::format("WideCharToMultiByte failed. Last error: {}", ::GetLastError()));
}
SPDLOG_INLINE void utf8_to_wstrbuf(string_view_t str, wmemory_buf_t &target) {
if (str.size() > static_cast<size_t>((std::numeric_limits<int>::max)()) - 1) {
throw_spdlog_ex("UTF-8 string is too big to be converted to UTF-16");
}
int str_size = static_cast<int>(str.size());
if (str_size == 0) {
target.resize(0);
return;
}
// find the size to allocate for the result buffer
int result_size = ::MultiByteToWideChar(CP_UTF8, 0, str.data(), str_size, NULL, 0);
if (result_size > 0) {
target.resize(result_size);
result_size =
::MultiByteToWideChar(CP_UTF8, 0, str.data(), str_size, target.data(), result_size);
if (result_size > 0) {
assert(result_size == static_cast<int>(target.size()));
return;
}
}
throw_spdlog_ex(
fmt_lib::format("MultiByteToWideChar failed. Last error: {}", ::GetLastError()));
}
#endif // (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) &&
// defined(_WIN32)
// return true on success
static SPDLOG_INLINE bool mkdir_(const filename_t &path) {
#ifdef _WIN32
#ifdef SPDLOG_WCHAR_FILENAMES
return ::_wmkdir(path.c_str()) == 0;
#else
return ::_mkdir(path.c_str()) == 0;
#endif
#else
return ::mkdir(path.c_str(), mode_t(0755)) == 0;
#endif
}
// create the given directory - and all directories leading to it
// return true on success or if the directory already exists
SPDLOG_INLINE bool create_dir(const filename_t &path) {
if (path_exists(path)) {
return true;
}
if (path.empty()) {
return false;
}
size_t search_offset = 0;
do {
auto token_pos = path.find_first_of(folder_seps_filename, search_offset);
// treat the entire path as a folder if no folder separator not found
if (token_pos == filename_t::npos) {
token_pos = path.size();
}
auto subdir = path.substr(0, token_pos);
#ifdef _WIN32
// if subdir is just a drive letter, add a slash e.g. "c:"=>"c:\",
// otherwise path_exists(subdir) returns false (issue #3079)
const bool is_drive = subdir.length() == 2 && subdir[1] == ':';
if (is_drive) {
subdir += '\\';
token_pos++;
}
#endif
if (!subdir.empty() && !path_exists(subdir) && !mkdir_(subdir)) {
return false; // return error if failed creating dir
}
search_offset = token_pos + 1;
} while (search_offset < path.size());
return true;
}
// Return directory name from given path or empty string
// "abc/file" => "abc"
// "abc/" => "abc"
// "abc" => ""
// "abc///" => "abc//"
SPDLOG_INLINE filename_t dir_name(const filename_t &path) {
auto pos = path.find_last_of(folder_seps_filename);
return pos != filename_t::npos ? path.substr(0, pos) : filename_t{};
}
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4996)
#endif // _MSC_VER
std::string SPDLOG_INLINE getenv(const char *field) {
#if defined(_MSC_VER) && defined(WINAPI_FAMILY) && defined(WINAPI_FAMILY_DESKTOP_APP) && \
(WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP)
return std::string{}; // not supported under uwp
#else
char *buf = std::getenv(field);
return buf ? buf : std::string{};
#endif
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif // _MSC_VER
// Do fsync by FILE handlerpointer
// Return true on success
SPDLOG_INLINE bool fsync(FILE *fp) {
#ifdef _WIN32
return FlushFileBuffers(reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(fp)))) != 0;
#else
return ::fsync(fileno(fp)) == 0;
#endif
}
// Do non-locking fwrite if possible by the os or use the regular locking fwrite
// Return true on success.
SPDLOG_INLINE bool fwrite_bytes(const void *ptr, const size_t n_bytes, FILE *fp) {
#if defined(_WIN32) && defined(SPDLOG_FWRITE_UNLOCKED)
return _fwrite_nolock(ptr, 1, n_bytes, fp) == n_bytes;
#elif defined(SPDLOG_FWRITE_UNLOCKED)
return ::fwrite_unlocked(ptr, 1, n_bytes, fp) == n_bytes;
#else
return std::fwrite(ptr, 1, n_bytes, fp) == n_bytes;
#endif
}
} // namespace os
} // namespace details
} // namespace spdlog
+127
View File
@@ -0,0 +1,127 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#include <ctime> // std::time_t
#include <spdlog/common.h>
namespace spdlog {
namespace details {
namespace os {
SPDLOG_API spdlog::log_clock::time_point now() SPDLOG_NOEXCEPT;
SPDLOG_API std::tm localtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT;
SPDLOG_API std::tm localtime() SPDLOG_NOEXCEPT;
SPDLOG_API std::tm gmtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT;
SPDLOG_API std::tm gmtime() SPDLOG_NOEXCEPT;
// eol definition
#if !defined(SPDLOG_EOL)
#ifdef _WIN32
#define SPDLOG_EOL "\r\n"
#else
#define SPDLOG_EOL "\n"
#endif
#endif
SPDLOG_CONSTEXPR static const char *default_eol = SPDLOG_EOL;
// folder separator
#if !defined(SPDLOG_FOLDER_SEPS)
#ifdef _WIN32
#define SPDLOG_FOLDER_SEPS "\\/"
#else
#define SPDLOG_FOLDER_SEPS "/"
#endif
#endif
SPDLOG_CONSTEXPR static const char folder_seps[] = SPDLOG_FOLDER_SEPS;
SPDLOG_CONSTEXPR static const filename_t::value_type folder_seps_filename[] =
SPDLOG_FILENAME_T(SPDLOG_FOLDER_SEPS);
// fopen_s on non windows for writing
SPDLOG_API bool fopen_s(FILE **fp, const filename_t &filename, const filename_t &mode);
// Remove filename. return 0 on success
SPDLOG_API int remove(const filename_t &filename) SPDLOG_NOEXCEPT;
// Remove file if exists. return 0 on success
// Note: Non atomic (might return failure to delete if concurrently deleted by other process/thread)
SPDLOG_API int remove_if_exists(const filename_t &filename) SPDLOG_NOEXCEPT;
SPDLOG_API int rename(const filename_t &filename1, const filename_t &filename2) SPDLOG_NOEXCEPT;
// Return if file exists.
SPDLOG_API bool path_exists(const filename_t &filename) SPDLOG_NOEXCEPT;
// Return file size according to open FILE* object
SPDLOG_API size_t filesize(FILE *f);
// Return utc offset in minutes or throw spdlog_ex on failure
SPDLOG_API int utc_minutes_offset(const std::tm &tm = details::os::localtime());
// Return current thread id as size_t
// It exists because the std::this_thread::get_id() is much slower(especially
// under VS 2013)
SPDLOG_API size_t _thread_id() SPDLOG_NOEXCEPT;
// Return current thread id as size_t (from thread local storage)
SPDLOG_API size_t thread_id() SPDLOG_NOEXCEPT;
// This is avoid msvc issue in sleep_for that happens if the clock changes.
// See https://github.com/gabime/spdlog/issues/609
SPDLOG_API void sleep_for_millis(unsigned int milliseconds) SPDLOG_NOEXCEPT;
SPDLOG_API std::string filename_to_str(const filename_t &filename);
SPDLOG_API int pid() SPDLOG_NOEXCEPT;
// Determine if the terminal supports colors
// Source: https://github.com/agauniyal/rang/
SPDLOG_API bool is_color_terminal() SPDLOG_NOEXCEPT;
// Determine if the terminal attached
// Source: https://github.com/agauniyal/rang/
SPDLOG_API bool in_terminal(FILE *file) SPDLOG_NOEXCEPT;
#if (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32)
SPDLOG_API void wstr_to_utf8buf(wstring_view_t wstr, memory_buf_t &target);
SPDLOG_API void utf8_to_wstrbuf(string_view_t str, wmemory_buf_t &target);
#endif
// Return directory name from given path or empty string
// "abc/file" => "abc"
// "abc/" => "abc"
// "abc" => ""
// "abc///" => "abc//"
SPDLOG_API filename_t dir_name(const filename_t &path);
// Create a dir from the given path.
// Return true if succeeded or if this dir already exists.
SPDLOG_API bool create_dir(const filename_t &path);
// non thread safe, cross platform getenv/getenv_s
// return empty string if field not found
SPDLOG_API std::string getenv(const char *field);
// Do fsync by FILE objectpointer.
// Return true on success.
SPDLOG_API bool fsync(FILE *fp);
// Do non-locking fwrite if possible by the os or use the regular locking fwrite
// Return true on success.
SPDLOG_API bool fwrite_bytes(const void *ptr, const size_t n_bytes, FILE *fp);
} // namespace os
} // namespace details
} // namespace spdlog
#ifdef SPDLOG_HEADER_ONLY
#include "os-inl.h"
#endif
@@ -0,0 +1,26 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#ifndef SPDLOG_HEADER_ONLY
#include <spdlog/details/periodic_worker.h>
#endif
namespace spdlog {
namespace details {
// stop the worker thread and join it
SPDLOG_INLINE periodic_worker::~periodic_worker() {
if (worker_thread_.joinable()) {
{
std::lock_guard<std::mutex> lock(mutex_);
active_ = false;
}
cv_.notify_one();
worker_thread_.join();
}
}
} // namespace details
} // namespace spdlog
@@ -0,0 +1,58 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
// periodic worker thread - periodically executes the given callback function.
//
// RAII over the owned thread:
// creates the thread on construction.
// stops and joins the thread on destruction (if the thread is executing a callback, wait for it
// to finish first).
#include <chrono>
#include <condition_variable>
#include <functional>
#include <mutex>
#include <thread>
namespace spdlog {
namespace details {
class SPDLOG_API periodic_worker {
public:
template <typename Rep, typename Period>
periodic_worker(const std::function<void()> &callback_fun,
std::chrono::duration<Rep, Period> interval) {
active_ = (interval > std::chrono::duration<Rep, Period>::zero());
if (!active_) {
return;
}
worker_thread_ = std::thread([this, callback_fun, interval]() {
for (;;) {
std::unique_lock<std::mutex> lock(this->mutex_);
if (this->cv_.wait_for(lock, interval, [this] { return !this->active_; })) {
return; // active_ == false, so exit this thread
}
callback_fun();
}
});
}
std::thread &get_thread() { return worker_thread_; }
periodic_worker(const periodic_worker &) = delete;
periodic_worker &operator=(const periodic_worker &) = delete;
// stop the worker thread and join it
~periodic_worker();
private:
bool active_;
std::thread worker_thread_;
std::mutex mutex_;
std::condition_variable cv_;
};
} // namespace details
} // namespace spdlog
#ifdef SPDLOG_HEADER_ONLY
#include "periodic_worker-inl.h"
#endif
+270
View File
@@ -0,0 +1,270 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#ifndef SPDLOG_HEADER_ONLY
#include <spdlog/details/registry.h>
#endif
#include <spdlog/common.h>
#include <spdlog/details/periodic_worker.h>
#include <spdlog/logger.h>
#include <spdlog/pattern_formatter.h>
#ifndef SPDLOG_DISABLE_DEFAULT_LOGGER
// support for the default stdout color logger
#ifdef _WIN32
#include <spdlog/sinks/wincolor_sink.h>
#else
#include <spdlog/sinks/ansicolor_sink.h>
#endif
#endif // SPDLOG_DISABLE_DEFAULT_LOGGER
#include <chrono>
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
namespace spdlog {
namespace details {
SPDLOG_INLINE registry::registry()
: formatter_(new pattern_formatter()) {
#ifndef SPDLOG_DISABLE_DEFAULT_LOGGER
// create default logger (ansicolor_stdout_sink_mt or wincolor_stdout_sink_mt in windows).
#ifdef _WIN32
auto color_sink = std::make_shared<sinks::wincolor_stdout_sink_mt>();
#else
auto color_sink = std::make_shared<sinks::ansicolor_stdout_sink_mt>();
#endif
const char *default_logger_name = "";
default_logger_ = std::make_shared<spdlog::logger>(default_logger_name, std::move(color_sink));
loggers_[default_logger_name] = default_logger_;
#endif // SPDLOG_DISABLE_DEFAULT_LOGGER
}
SPDLOG_INLINE registry::~registry() = default;
SPDLOG_INLINE void registry::register_logger(std::shared_ptr<logger> new_logger) {
std::lock_guard<std::mutex> lock(logger_map_mutex_);
register_logger_(std::move(new_logger));
}
SPDLOG_INLINE void registry::register_or_replace(std::shared_ptr<logger> new_logger) {
std::lock_guard<std::mutex> lock(logger_map_mutex_);
register_or_replace_(std::move(new_logger));
}
SPDLOG_INLINE void registry::initialize_logger(std::shared_ptr<logger> new_logger) {
std::lock_guard<std::mutex> lock(logger_map_mutex_);
new_logger->set_formatter(formatter_->clone());
if (err_handler_) {
new_logger->set_error_handler(err_handler_);
}
// set new level according to previously configured level or default level
auto it = log_levels_.find(new_logger->name());
auto new_level = it != log_levels_.end() ? it->second : global_log_level_;
new_logger->set_level(new_level);
new_logger->flush_on(flush_level_);
if (backtrace_n_messages_ > 0) {
new_logger->enable_backtrace(backtrace_n_messages_);
}
if (automatic_registration_) {
register_logger_(std::move(new_logger));
}
}
SPDLOG_INLINE std::shared_ptr<logger> registry::get(const std::string &logger_name) {
std::lock_guard<std::mutex> lock(logger_map_mutex_);
auto found = loggers_.find(logger_name);
return found == loggers_.end() ? nullptr : found->second;
}
SPDLOG_INLINE std::shared_ptr<logger> registry::default_logger() {
std::lock_guard<std::mutex> lock(logger_map_mutex_);
return default_logger_;
}
// Return raw ptr to the default logger.
// To be used directly by the spdlog default api (e.g. spdlog::info)
// This make the default API faster, but cannot be used concurrently with set_default_logger().
// e.g do not call set_default_logger() from one thread while calling spdlog::info() from another.
SPDLOG_INLINE logger *registry::get_default_raw() { return default_logger_.get(); }
// set default logger.
// the default logger is stored in default_logger_ (for faster retrieval) and in the loggers_ map.
SPDLOG_INLINE void registry::set_default_logger(std::shared_ptr<logger> new_default_logger) {
std::lock_guard<std::mutex> lock(logger_map_mutex_);
if (new_default_logger != nullptr) {
loggers_[new_default_logger->name()] = new_default_logger;
}
default_logger_ = std::move(new_default_logger);
}
SPDLOG_INLINE void registry::set_tp(std::shared_ptr<thread_pool> tp) {
std::lock_guard<std::recursive_mutex> lock(tp_mutex_);
tp_ = std::move(tp);
}
SPDLOG_INLINE std::shared_ptr<thread_pool> registry::get_tp() {
std::lock_guard<std::recursive_mutex> lock(tp_mutex_);
return tp_;
}
// Set global formatter. Each sink in each logger will get a clone of this object
SPDLOG_INLINE void registry::set_formatter(std::unique_ptr<formatter> formatter) {
std::lock_guard<std::mutex> lock(logger_map_mutex_);
formatter_ = std::move(formatter);
for (auto &l : loggers_) {
l.second->set_formatter(formatter_->clone());
}
}
SPDLOG_INLINE void registry::enable_backtrace(size_t n_messages) {
std::lock_guard<std::mutex> lock(logger_map_mutex_);
backtrace_n_messages_ = n_messages;
for (auto &l : loggers_) {
l.second->enable_backtrace(n_messages);
}
}
SPDLOG_INLINE void registry::disable_backtrace() {
std::lock_guard<std::mutex> lock(logger_map_mutex_);
backtrace_n_messages_ = 0;
for (auto &l : loggers_) {
l.second->disable_backtrace();
}
}
SPDLOG_INLINE void registry::set_level(level::level_enum log_level) {
std::lock_guard<std::mutex> lock(logger_map_mutex_);
for (auto &l : loggers_) {
l.second->set_level(log_level);
}
global_log_level_ = log_level;
}
SPDLOG_INLINE void registry::flush_on(level::level_enum log_level) {
std::lock_guard<std::mutex> lock(logger_map_mutex_);
for (auto &l : loggers_) {
l.second->flush_on(log_level);
}
flush_level_ = log_level;
}
SPDLOG_INLINE void registry::set_error_handler(err_handler handler) {
std::lock_guard<std::mutex> lock(logger_map_mutex_);
for (auto &l : loggers_) {
l.second->set_error_handler(handler);
}
err_handler_ = std::move(handler);
}
SPDLOG_INLINE void registry::apply_all(
const std::function<void(const std::shared_ptr<logger>)> &fun) {
std::lock_guard<std::mutex> lock(logger_map_mutex_);
for (auto &l : loggers_) {
fun(l.second);
}
}
SPDLOG_INLINE void registry::flush_all() {
std::lock_guard<std::mutex> lock(logger_map_mutex_);
for (auto &l : loggers_) {
l.second->flush();
}
}
SPDLOG_INLINE void registry::drop(const std::string &logger_name) {
std::lock_guard<std::mutex> lock(logger_map_mutex_);
auto is_default_logger = default_logger_ && default_logger_->name() == logger_name;
loggers_.erase(logger_name);
if (is_default_logger) {
default_logger_.reset();
}
}
SPDLOG_INLINE void registry::drop_all() {
std::lock_guard<std::mutex> lock(logger_map_mutex_);
loggers_.clear();
default_logger_.reset();
}
// clean all resources and threads started by the registry
SPDLOG_INLINE void registry::shutdown() {
{
std::lock_guard<std::mutex> lock(flusher_mutex_);
periodic_flusher_.reset();
}
drop_all();
{
std::lock_guard<std::recursive_mutex> lock(tp_mutex_);
tp_.reset();
}
}
SPDLOG_INLINE std::recursive_mutex &registry::tp_mutex() { return tp_mutex_; }
SPDLOG_INLINE void registry::set_automatic_registration(bool automatic_registration) {
std::lock_guard<std::mutex> lock(logger_map_mutex_);
automatic_registration_ = automatic_registration;
}
SPDLOG_INLINE void registry::set_levels(log_levels levels, level::level_enum *global_level) {
std::lock_guard<std::mutex> lock(logger_map_mutex_);
log_levels_ = std::move(levels);
auto global_level_requested = global_level != nullptr;
global_log_level_ = global_level_requested ? *global_level : global_log_level_;
for (auto &logger : loggers_) {
auto logger_entry = log_levels_.find(logger.first);
if (logger_entry != log_levels_.end()) {
logger.second->set_level(logger_entry->second);
} else if (global_level_requested) {
logger.second->set_level(*global_level);
}
}
}
SPDLOG_INLINE registry &registry::instance() {
static registry s_instance;
return s_instance;
}
SPDLOG_INLINE void registry::apply_logger_env_levels(std::shared_ptr<logger> new_logger) {
std::lock_guard<std::mutex> lock(logger_map_mutex_);
auto it = log_levels_.find(new_logger->name());
auto new_level = it != log_levels_.end() ? it->second : global_log_level_;
new_logger->set_level(new_level);
}
SPDLOG_INLINE void registry::throw_if_exists_(const std::string &logger_name) {
if (loggers_.find(logger_name) != loggers_.end()) {
throw_spdlog_ex("logger with name '" + logger_name + "' already exists");
}
}
SPDLOG_INLINE void registry::register_logger_(std::shared_ptr<logger> new_logger) {
auto &logger_name = new_logger->name();
throw_if_exists_(logger_name);
loggers_[logger_name] = std::move(new_logger);
}
SPDLOG_INLINE void registry::register_or_replace_(std::shared_ptr<logger> new_logger) {
loggers_[new_logger->name()] = std::move(new_logger);
}
} // namespace details
} // namespace spdlog
+131
View File
@@ -0,0 +1,131 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
// Loggers registry of unique name->logger pointer
// An attempt to create a logger with an already existing name will result with spdlog_ex exception.
// If user requests a non existing logger, nullptr will be returned
// This class is thread safe
#include <spdlog/common.h>
#include <spdlog/details/periodic_worker.h>
#include <chrono>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
namespace spdlog {
class logger;
namespace details {
class thread_pool;
class SPDLOG_API registry {
public:
using log_levels = std::unordered_map<std::string, level::level_enum>;
registry(const registry &) = delete;
registry &operator=(const registry &) = delete;
void register_logger(std::shared_ptr<logger> new_logger);
void register_or_replace(std::shared_ptr<logger> new_logger);
void initialize_logger(std::shared_ptr<logger> new_logger);
std::shared_ptr<logger> get(const std::string &logger_name);
std::shared_ptr<logger> default_logger();
// Return raw ptr to the default logger.
// To be used directly by the spdlog default api (e.g. spdlog::info)
// This make the default API faster, but cannot be used concurrently with set_default_logger().
// e.g do not call set_default_logger() from one thread while calling spdlog::info() from
// another.
logger *get_default_raw();
// set default logger and add it to the registry if not registered already.
// default logger is stored in default_logger_ (for faster retrieval) and in the loggers_ map.
// Note: Make sure to unregister it when no longer needed or before calling again with a new
// logger.
void set_default_logger(std::shared_ptr<logger> new_default_logger);
void set_tp(std::shared_ptr<thread_pool> tp);
std::shared_ptr<thread_pool> get_tp();
// Set global formatter. Each sink in each logger will get a clone of this object
void set_formatter(std::unique_ptr<formatter> formatter);
void enable_backtrace(size_t n_messages);
void disable_backtrace();
void set_level(level::level_enum log_level);
void flush_on(level::level_enum log_level);
template <typename Rep, typename Period>
void flush_every(std::chrono::duration<Rep, Period> interval) {
std::lock_guard<std::mutex> lock(flusher_mutex_);
auto clbk = [this]() { this->flush_all(); };
periodic_flusher_ = details::make_unique<periodic_worker>(clbk, interval);
}
std::unique_ptr<periodic_worker> &get_flusher() {
std::lock_guard<std::mutex> lock(flusher_mutex_);
return periodic_flusher_;
}
void set_error_handler(err_handler handler);
void apply_all(const std::function<void(const std::shared_ptr<logger>)> &fun);
void flush_all();
void drop(const std::string &logger_name);
void drop_all();
// clean all resources and threads started by the registry
void shutdown();
std::recursive_mutex &tp_mutex();
void set_automatic_registration(bool automatic_registration);
// set levels for all existing/future loggers. global_level can be null if should not set.
void set_levels(log_levels levels, level::level_enum *global_level);
static registry &instance();
void apply_logger_env_levels(std::shared_ptr<logger> new_logger);
private:
registry();
~registry();
void throw_if_exists_(const std::string &logger_name);
void register_logger_(std::shared_ptr<logger> new_logger);
void register_or_replace_(std::shared_ptr<logger> new_logger);
bool set_level_from_cfg_(logger *logger);
std::mutex logger_map_mutex_, flusher_mutex_;
std::recursive_mutex tp_mutex_;
std::unordered_map<std::string, std::shared_ptr<logger>> loggers_;
log_levels log_levels_;
std::unique_ptr<formatter> formatter_;
spdlog::level::level_enum global_log_level_ = level::info;
level::level_enum flush_level_ = level::off;
err_handler err_handler_;
std::shared_ptr<thread_pool> tp_;
std::unique_ptr<periodic_worker> periodic_flusher_;
std::shared_ptr<logger> default_logger_;
bool automatic_registration_ = true;
size_t backtrace_n_messages_ = 0;
};
} // namespace details
} // namespace spdlog
#ifdef SPDLOG_HEADER_ONLY
#include "registry-inl.h"
#endif
@@ -0,0 +1,22 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#include "registry.h"
namespace spdlog {
// Default logger factory- creates synchronous loggers
class logger;
struct synchronous_factory {
template <typename Sink, typename... SinkArgs>
static std::shared_ptr<spdlog::logger> create(std::string logger_name, SinkArgs &&...args) {
auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...);
auto new_logger = std::make_shared<spdlog::logger>(std::move(logger_name), std::move(sink));
details::registry::instance().initialize_logger(new_logger);
return new_logger;
}
};
} // namespace spdlog
@@ -0,0 +1,217 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#define WIN32_LEAN_AND_MEAN
// tcp client helper
#include <spdlog/common.h>
#include <spdlog/details/os.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "Mswsock.lib")
#pragma comment(lib, "AdvApi32.lib")
namespace spdlog {
namespace details {
class tcp_client {
SOCKET socket_ = INVALID_SOCKET;
static void init_winsock_() {
WSADATA wsaData;
auto rv = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (rv != 0) {
throw_winsock_error_("WSAStartup failed", ::WSAGetLastError());
}
}
static void throw_winsock_error_(const std::string &msg, int last_error) {
char buf[512];
::FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
last_error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf,
(sizeof(buf) / sizeof(char)), NULL);
throw_spdlog_ex(fmt_lib::format("tcp_sink - {}: {}", msg, buf));
}
public:
tcp_client() { init_winsock_(); }
~tcp_client() {
close();
::WSACleanup();
}
bool is_connected() const { return socket_ != INVALID_SOCKET; }
void close() {
::closesocket(socket_);
socket_ = INVALID_SOCKET;
}
SOCKET fd() const { return socket_; }
int connect_socket_with_timeout(SOCKET sockfd,
const struct sockaddr *addr,
int addrlen,
const timeval &tv) {
// If no timeout requested, do a normal blocking connect.
if (tv.tv_sec == 0 && tv.tv_usec == 0) {
int rv = ::connect(sockfd, addr, addrlen);
if (rv == SOCKET_ERROR && WSAGetLastError() == WSAEISCONN) {
return 0;
}
return rv;
}
// Switch to non‐blocking mode
u_long mode = 1UL;
if (::ioctlsocket(sockfd, FIONBIO, &mode) == SOCKET_ERROR) {
return SOCKET_ERROR;
}
int rv = ::connect(sockfd, addr, addrlen);
int last_error = WSAGetLastError();
if (rv == 0 || last_error == WSAEISCONN) {
mode = 0UL;
if (::ioctlsocket(sockfd, FIONBIO, &mode) == SOCKET_ERROR) {
return SOCKET_ERROR;
}
return 0;
}
if (last_error != WSAEWOULDBLOCK) {
// Real error
mode = 0UL;
if (::ioctlsocket(sockfd, FIONBIO, &mode)) {
return SOCKET_ERROR;
}
return SOCKET_ERROR;
}
// Wait until socket is writable or timeout expires
fd_set wfds;
FD_ZERO(&wfds);
FD_SET(sockfd, &wfds);
rv = ::select(0, nullptr, &wfds, nullptr, const_cast<timeval *>(&tv));
// Restore blocking mode regardless of select result
mode = 0UL;
if (::ioctlsocket(sockfd, FIONBIO, &mode) == SOCKET_ERROR) {
return SOCKET_ERROR;
}
if (rv == 0) {
WSASetLastError(WSAETIMEDOUT);
return SOCKET_ERROR;
}
if (rv == SOCKET_ERROR) {
return SOCKET_ERROR;
}
int so_error = 0;
int len = sizeof(so_error);
if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, reinterpret_cast<char *>(&so_error), &len) ==
SOCKET_ERROR) {
return SOCKET_ERROR;
}
if (so_error != 0 && so_error != WSAEISCONN) {
// connection failed
WSASetLastError(so_error);
return SOCKET_ERROR;
}
return 0; // success
}
// try to connect or throw on failure
void connect(const std::string &host, int port, int timeout_ms = 0) {
if (is_connected()) {
close();
}
struct addrinfo hints {};
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_UNSPEC; // To work with IPv4, IPv6, and so on
hints.ai_socktype = SOCK_STREAM; // TCP
hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value
hints.ai_protocol = 0;
timeval tv;
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
auto port_str = std::to_string(port);
struct addrinfo *addrinfo_result;
auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result);
int last_error = 0;
if (rv != 0) {
last_error = ::WSAGetLastError();
WSACleanup();
throw_winsock_error_("getaddrinfo failed", last_error);
}
// Try each address until we successfully connect(2).
for (auto *rp = addrinfo_result; rp != nullptr; rp = rp->ai_next) {
socket_ = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (socket_ == INVALID_SOCKET) {
last_error = ::WSAGetLastError();
WSACleanup();
continue;
}
if (connect_socket_with_timeout(socket_, rp->ai_addr, (int)rp->ai_addrlen, tv) == 0) {
last_error = 0;
break;
}
last_error = WSAGetLastError();
::closesocket(socket_);
socket_ = INVALID_SOCKET;
}
::freeaddrinfo(addrinfo_result);
if (socket_ == INVALID_SOCKET) {
WSACleanup();
throw_winsock_error_("connect failed", last_error);
}
if (timeout_ms > 0) {
DWORD tv = static_cast<DWORD>(timeout_ms);
::setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv));
::setsockopt(socket_, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof(tv));
}
// set TCP_NODELAY
int enable_flag = 1;
::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&enable_flag),
sizeof(enable_flag));
}
// Send exactly n_bytes of the given data.
// On error close the connection and throw.
void send(const char *data, size_t n_bytes) {
size_t bytes_sent = 0;
while (bytes_sent < n_bytes) {
const int send_flags = 0;
auto write_result =
::send(socket_, data + bytes_sent, (int)(n_bytes - bytes_sent), send_flags);
if (write_result == SOCKET_ERROR) {
int last_error = ::WSAGetLastError();
close();
throw_winsock_error_("send failed", last_error);
}
if (write_result == 0) // (probably should not happen but in any case..)
{
break;
}
bytes_sent += static_cast<size_t>(write_result);
}
}
};
} // namespace details
} // namespace spdlog
+203
View File
@@ -0,0 +1,203 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#ifdef _WIN32
#error include tcp_client-windows.h instead
#endif
// tcp client helper
#include <spdlog/common.h>
#include <spdlog/details/os.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>
#include <string>
namespace spdlog {
namespace details {
class tcp_client {
int socket_ = -1;
public:
bool is_connected() const { return socket_ != -1; }
void close() {
if (is_connected()) {
::close(socket_);
socket_ = -1;
}
}
int fd() const { return socket_; }
~tcp_client() { close(); }
int connect_socket_with_timeout(int sockfd,
const struct sockaddr *addr,
socklen_t addrlen,
const timeval &tv) {
// Blocking connect if timeout is zero
if (tv.tv_sec == 0 && tv.tv_usec == 0) {
int rv = ::connect(sockfd, addr, addrlen);
if (rv < 0 && errno == EISCONN) {
// already connected, treat as success
return 0;
}
return rv;
}
// Non-blocking path
int orig_flags = ::fcntl(sockfd, F_GETFL, 0);
if (orig_flags < 0) {
return -1;
}
if (::fcntl(sockfd, F_SETFL, orig_flags | O_NONBLOCK) < 0) {
return -1;
}
int rv = ::connect(sockfd, addr, addrlen);
if (rv == 0 || (rv < 0 && errno == EISCONN)) {
// immediate connect or already connected
::fcntl(sockfd, F_SETFL, orig_flags);
return 0;
}
if (errno != EINPROGRESS) {
::fcntl(sockfd, F_SETFL, orig_flags);
return -1;
}
// wait for writability
fd_set wfds;
FD_ZERO(&wfds);
FD_SET(sockfd, &wfds);
struct timeval tv_copy = tv;
rv = ::select(sockfd + 1, nullptr, &wfds, nullptr, &tv_copy);
if (rv <= 0) {
// timeout or error
::fcntl(sockfd, F_SETFL, orig_flags);
if (rv == 0) errno = ETIMEDOUT;
return -1;
}
// check socket error
int so_error = 0;
socklen_t len = sizeof(so_error);
if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &so_error, &len) < 0) {
::fcntl(sockfd, F_SETFL, orig_flags);
return -1;
}
::fcntl(sockfd, F_SETFL, orig_flags);
if (so_error != 0 && so_error != EISCONN) {
errno = so_error;
return -1;
}
return 0;
}
// try to connect or throw on failure
void connect(const std::string &host, int port, int timeout_ms = 0) {
close();
struct addrinfo hints {};
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC; // To work with IPv4, IPv6, and so on
hints.ai_socktype = SOCK_STREAM; // TCP
hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value
hints.ai_protocol = 0;
struct timeval tv;
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
auto port_str = std::to_string(port);
struct addrinfo *addrinfo_result;
auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result);
if (rv != 0) {
throw_spdlog_ex(fmt_lib::format("::getaddrinfo failed: {}", gai_strerror(rv)));
}
// Try each address until we successfully connect(2).
int last_errno = 0;
for (auto *rp = addrinfo_result; rp != nullptr; rp = rp->ai_next) {
#if defined(SOCK_CLOEXEC)
const int flags = SOCK_CLOEXEC;
#else
const int flags = 0;
#endif
socket_ = ::socket(rp->ai_family, rp->ai_socktype | flags, rp->ai_protocol);
if (socket_ == -1) {
last_errno = errno;
continue;
}
::fcntl(socket_, F_SETFD, FD_CLOEXEC);
if (connect_socket_with_timeout(socket_, rp->ai_addr, rp->ai_addrlen, tv) == 0) {
last_errno = 0;
break;
}
last_errno = errno;
::close(socket_);
socket_ = -1;
}
::freeaddrinfo(addrinfo_result);
if (socket_ == -1) {
throw_spdlog_ex("::connect failed", last_errno);
}
if (timeout_ms > 0) {
// Set timeouts for send and recv
::setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv));
::setsockopt(socket_, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof(tv));
}
// set TCP_NODELAY
int enable_flag = 1;
::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&enable_flag),
sizeof(enable_flag));
// prevent sigpipe on systems where MSG_NOSIGNAL is not available
#if defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL)
::setsockopt(socket_, SOL_SOCKET, SO_NOSIGPIPE, reinterpret_cast<char *>(&enable_flag),
sizeof(enable_flag));
#endif
#if !defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL)
#error "tcp_sink would raise SIGPIPE since neither SO_NOSIGPIPE nor MSG_NOSIGNAL are available"
#endif
}
// Send exactly n_bytes of the given data.
// On error close the connection and throw.
void send(const char *data, size_t n_bytes) {
size_t bytes_sent = 0;
while (bytes_sent < n_bytes) {
#if defined(MSG_NOSIGNAL)
const int send_flags = MSG_NOSIGNAL;
#else
const int send_flags = 0;
#endif
auto write_result =
::send(socket_, data + bytes_sent, n_bytes - bytes_sent, send_flags);
if (write_result < 0) {
close();
throw_spdlog_ex("write(2) failed", errno);
}
if (write_result == 0) // (probably should not happen but in any case..)
{
break;
}
bytes_sent += static_cast<size_t>(write_result);
}
}
};
} // namespace details
} // namespace spdlog
@@ -0,0 +1,125 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#ifndef SPDLOG_HEADER_ONLY
#include <spdlog/details/thread_pool.h>
#endif
#include <cassert>
#include <spdlog/common.h>
namespace spdlog {
namespace details {
SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items,
size_t threads_n,
std::function<void()> on_thread_start,
std::function<void()> on_thread_stop)
: q_(q_max_items) {
if (threads_n == 0 || threads_n > 1000) {
throw_spdlog_ex(
"spdlog::thread_pool(): invalid threads_n param (valid "
"range is 1-1000)");
}
for (size_t i = 0; i < threads_n; i++) {
threads_.emplace_back([this, on_thread_start, on_thread_stop] {
on_thread_start();
this->thread_pool::worker_loop_();
on_thread_stop();
});
}
}
SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items,
size_t threads_n,
std::function<void()> on_thread_start)
: thread_pool(q_max_items, threads_n, std::move(on_thread_start), [] {}) {}
SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items, size_t threads_n)
: thread_pool(q_max_items, threads_n, [] {}, [] {}) {}
// message all threads to terminate gracefully join them
SPDLOG_INLINE thread_pool::~thread_pool() {
SPDLOG_TRY {
for (size_t i = 0; i < threads_.size(); i++) {
post_async_msg_(async_msg(async_msg_type::terminate), async_overflow_policy::block);
}
for (auto &t : threads_) {
t.join();
}
}
SPDLOG_CATCH_STD
}
void SPDLOG_INLINE thread_pool::post_log(async_logger_ptr &&worker_ptr,
const details::log_msg &msg,
async_overflow_policy overflow_policy) {
async_msg async_m(std::move(worker_ptr), async_msg_type::log, msg);
post_async_msg_(std::move(async_m), overflow_policy);
}
void SPDLOG_INLINE thread_pool::post_flush(async_logger_ptr &&worker_ptr,
async_overflow_policy overflow_policy) {
post_async_msg_(async_msg(std::move(worker_ptr), async_msg_type::flush), overflow_policy);
}
size_t SPDLOG_INLINE thread_pool::overrun_counter() { return q_.overrun_counter(); }
void SPDLOG_INLINE thread_pool::reset_overrun_counter() { q_.reset_overrun_counter(); }
size_t SPDLOG_INLINE thread_pool::discard_counter() { return q_.discard_counter(); }
void SPDLOG_INLINE thread_pool::reset_discard_counter() { q_.reset_discard_counter(); }
size_t SPDLOG_INLINE thread_pool::queue_size() { return q_.size(); }
void SPDLOG_INLINE thread_pool::post_async_msg_(async_msg &&new_msg,
async_overflow_policy overflow_policy) {
if (overflow_policy == async_overflow_policy::block) {
q_.enqueue(std::move(new_msg));
} else if (overflow_policy == async_overflow_policy::overrun_oldest) {
q_.enqueue_nowait(std::move(new_msg));
} else {
assert(overflow_policy == async_overflow_policy::discard_new);
q_.enqueue_if_have_room(std::move(new_msg));
}
}
void SPDLOG_INLINE thread_pool::worker_loop_() {
while (process_next_msg_()) {
}
}
// process next message in the queue
// returns true if this thread should still be active (while no terminated msg was received)
bool SPDLOG_INLINE thread_pool::process_next_msg_() {
async_msg incoming_async_msg;
q_.dequeue(incoming_async_msg);
switch (incoming_async_msg.msg_type) {
case async_msg_type::log: {
incoming_async_msg.worker_ptr->backend_sink_it_(incoming_async_msg);
return true;
}
case async_msg_type::flush: {
incoming_async_msg.worker_ptr->backend_flush_();
return true;
}
case async_msg_type::terminate: {
return false;
}
default: {
assert(false);
}
}
return true;
}
} // namespace details
} // namespace spdlog
+117
View File
@@ -0,0 +1,117 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#include <spdlog/details/log_msg_buffer.h>
#include <spdlog/details/mpmc_blocking_q.h>
#include <spdlog/details/os.h>
#include <chrono>
#include <functional>
#include <memory>
#include <thread>
#include <vector>
namespace spdlog {
class async_logger;
namespace details {
using async_logger_ptr = std::shared_ptr<spdlog::async_logger>;
enum class async_msg_type { log, flush, terminate };
// Async msg to move to/from the queue
// Movable only. should never be copied
struct async_msg : log_msg_buffer {
async_msg_type msg_type{async_msg_type::log};
async_logger_ptr worker_ptr;
async_msg() = default;
~async_msg() = default;
// should only be moved in or out of the queue..
async_msg(const async_msg &) = delete;
// support for vs2013 move
#if defined(_MSC_VER) && _MSC_VER <= 1800
async_msg(async_msg &&other)
: log_msg_buffer(std::move(other)),
msg_type(other.msg_type),
worker_ptr(std::move(other.worker_ptr)) {}
async_msg &operator=(async_msg &&other) {
*static_cast<log_msg_buffer *>(this) = std::move(other);
msg_type = other.msg_type;
worker_ptr = std::move(other.worker_ptr);
return *this;
}
#else // (_MSC_VER) && _MSC_VER <= 1800
async_msg(async_msg &&) = default;
async_msg &operator=(async_msg &&) = default;
#endif
// construct from log_msg with given type
async_msg(async_logger_ptr &&worker, async_msg_type the_type, const details::log_msg &m)
: log_msg_buffer{m},
msg_type{the_type},
worker_ptr{std::move(worker)} {}
async_msg(async_logger_ptr &&worker, async_msg_type the_type)
: log_msg_buffer{},
msg_type{the_type},
worker_ptr{std::move(worker)} {}
explicit async_msg(async_msg_type the_type)
: async_msg{nullptr, the_type} {}
};
class SPDLOG_API thread_pool {
public:
using item_type = async_msg;
using q_type = details::mpmc_blocking_queue<item_type>;
thread_pool(size_t q_max_items,
size_t threads_n,
std::function<void()> on_thread_start,
std::function<void()> on_thread_stop);
thread_pool(size_t q_max_items, size_t threads_n, std::function<void()> on_thread_start);
thread_pool(size_t q_max_items, size_t threads_n);
// message all threads to terminate gracefully and join them
~thread_pool();
thread_pool(const thread_pool &) = delete;
thread_pool &operator=(thread_pool &&) = delete;
void post_log(async_logger_ptr &&worker_ptr,
const details::log_msg &msg,
async_overflow_policy overflow_policy);
void post_flush(async_logger_ptr &&worker_ptr, async_overflow_policy overflow_policy);
size_t overrun_counter();
void reset_overrun_counter();
size_t discard_counter();
void reset_discard_counter();
size_t queue_size();
private:
q_type q_;
std::vector<std::thread> threads_;
void post_async_msg_(async_msg &&new_msg, async_overflow_policy overflow_policy);
void worker_loop_();
// process next message in the queue
// return true if this thread should still be active (while no terminate msg
// was received)
bool process_next_msg_();
};
} // namespace details
} // namespace spdlog
#ifdef SPDLOG_HEADER_ONLY
#include "thread_pool-inl.h"
#endif
@@ -0,0 +1,98 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
// Helper RAII over winsock udp client socket.
// Will throw on construction if socket creation failed.
#include <spdlog/common.h>
#include <spdlog/details/os.h>
#include <spdlog/details/windows_include.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <winsock2.h>
#include <ws2tcpip.h>
#if defined(_MSC_VER)
#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "Mswsock.lib")
#pragma comment(lib, "AdvApi32.lib")
#endif
namespace spdlog {
namespace details {
class udp_client {
static constexpr int TX_BUFFER_SIZE = 1024 * 10;
SOCKET socket_ = INVALID_SOCKET;
sockaddr_in addr_ = {};
static void init_winsock_() {
WSADATA wsaData;
auto rv = ::WSAStartup(MAKEWORD(2, 2), &wsaData);
if (rv != 0) {
throw_winsock_error_("WSAStartup failed", ::WSAGetLastError());
}
}
static void throw_winsock_error_(const std::string &msg, int last_error) {
char buf[512];
::FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
last_error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf,
(sizeof(buf) / sizeof(char)), NULL);
throw_spdlog_ex(fmt_lib::format("udp_sink - {}: {}", msg, buf));
}
void cleanup_() {
if (socket_ != INVALID_SOCKET) {
::closesocket(socket_);
}
socket_ = INVALID_SOCKET;
::WSACleanup();
}
public:
udp_client(const std::string &host, uint16_t port) {
init_winsock_();
addr_.sin_family = PF_INET;
addr_.sin_port = htons(port);
addr_.sin_addr.s_addr = INADDR_ANY;
if (InetPtonA(PF_INET, host.c_str(), &addr_.sin_addr.s_addr) != 1) {
int last_error = ::WSAGetLastError();
::WSACleanup();
throw_winsock_error_("error: Invalid address!", last_error);
}
socket_ = ::socket(PF_INET, SOCK_DGRAM, 0);
if (socket_ == INVALID_SOCKET) {
int last_error = ::WSAGetLastError();
::WSACleanup();
throw_winsock_error_("error: Create Socket failed", last_error);
}
int option_value = TX_BUFFER_SIZE;
if (::setsockopt(socket_, SOL_SOCKET, SO_SNDBUF,
reinterpret_cast<const char *>(&option_value), sizeof(option_value)) < 0) {
int last_error = ::WSAGetLastError();
cleanup_();
throw_winsock_error_("error: setsockopt(SO_SNDBUF) Failed!", last_error);
}
}
~udp_client() { cleanup_(); }
SOCKET fd() const { return socket_; }
void send(const char *data, size_t n_bytes) {
socklen_t tolen = sizeof(struct sockaddr);
if (::sendto(socket_, data, static_cast<int>(n_bytes), 0, (struct sockaddr *)&addr_,
tolen) == -1) {
throw_spdlog_ex("sendto(2) failed", errno);
}
}
};
} // namespace details
} // namespace spdlog
+81
View File
@@ -0,0 +1,81 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
// Helper RAII over unix udp client socket.
// Will throw on construction if the socket creation failed.
#ifdef _WIN32
#error "include udp_client-windows.h instead"
#endif
#include <arpa/inet.h>
#include <cstring>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <spdlog/common.h>
#include <spdlog/details/os.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string>
namespace spdlog {
namespace details {
class udp_client {
static constexpr int TX_BUFFER_SIZE = 1024 * 10;
int socket_ = -1;
struct sockaddr_in sockAddr_;
void cleanup_() {
if (socket_ != -1) {
::close(socket_);
socket_ = -1;
}
}
public:
udp_client(const std::string &host, uint16_t port) {
socket_ = ::socket(PF_INET, SOCK_DGRAM, 0);
if (socket_ < 0) {
throw_spdlog_ex("error: Create Socket Failed!");
}
int option_value = TX_BUFFER_SIZE;
if (::setsockopt(socket_, SOL_SOCKET, SO_SNDBUF,
reinterpret_cast<const char *>(&option_value), sizeof(option_value)) < 0) {
cleanup_();
throw_spdlog_ex("error: setsockopt(SO_SNDBUF) Failed!");
}
sockAddr_.sin_family = AF_INET;
sockAddr_.sin_port = htons(port);
if (::inet_aton(host.c_str(), &sockAddr_.sin_addr) == 0) {
cleanup_();
throw_spdlog_ex("error: Invalid address!");
}
::memset(sockAddr_.sin_zero, 0x00, sizeof(sockAddr_.sin_zero));
}
~udp_client() { cleanup_(); }
int fd() const { return socket_; }
// Send exactly n_bytes of the given data.
// On error close the connection and throw.
void send(const char *data, size_t n_bytes) {
ssize_t toslen = 0;
socklen_t tolen = sizeof(struct sockaddr);
if ((toslen = ::sendto(socket_, data, n_bytes, 0, (struct sockaddr *)&sockAddr_, tolen)) ==
-1) {
throw_spdlog_ex("sendto(2) failed", errno);
}
}
};
} // namespace details
} // namespace spdlog
@@ -0,0 +1,11 @@
#pragma once
#ifndef NOMINMAX
#define NOMINMAX // prevent windows redefining min/max
#endif
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
+224
View File
@@ -0,0 +1,224 @@
//
// Copyright(c) 2015 Gabi Melman.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
//
#pragma once
#include <cctype>
#include <spdlog/common.h>
#if defined(__has_include)
#if __has_include(<version>)
#include <version>
#endif
#endif
#if __cpp_lib_span >= 202002L
#include <span>
#endif
//
// Support for logging binary data as hex
// format flags, any combination of the following:
// {:X} - print in uppercase.
// {:s} - don't separate each byte with space.
// {:p} - don't print the position on each line start.
// {:n} - don't split the output to lines.
// {:a} - show ASCII if :n is not set
//
// Examples:
//
// std::vector<char> v(200, 0x0b);
// logger->info("Some buffer {}", spdlog::to_hex(v));
// char buf[128];
// logger->info("Some buffer {:X}", spdlog::to_hex(std::begin(buf), std::end(buf)));
// logger->info("Some buffer {:X}", spdlog::to_hex(std::begin(buf), std::end(buf), 16));
namespace spdlog {
namespace details {
template <typename It>
class dump_info {
public:
dump_info(It range_begin, It range_end, size_t size_per_line)
: begin_(range_begin),
end_(range_end),
size_per_line_(size_per_line) {}
// do not use begin() and end() to avoid collision with fmt/ranges
It get_begin() const { return begin_; }
It get_end() const { return end_; }
size_t size_per_line() const { return size_per_line_; }
private:
It begin_, end_;
size_t size_per_line_;
};
} // namespace details
// create a dump_info that wraps the given container
template <typename Container>
inline details::dump_info<typename Container::const_iterator> to_hex(const Container &container,
size_t size_per_line = 32) {
static_assert(sizeof(typename Container::value_type) == 1,
"sizeof(Container::value_type) != 1");
using Iter = typename Container::const_iterator;
return details::dump_info<Iter>(std::begin(container), std::end(container), size_per_line);
}
#if __cpp_lib_span >= 202002L
template <typename Value, size_t Extent>
inline details::dump_info<typename std::span<Value, Extent>::iterator> to_hex(
const std::span<Value, Extent> &container, size_t size_per_line = 32) {
using Container = std::span<Value, Extent>;
static_assert(sizeof(typename Container::value_type) == 1,
"sizeof(Container::value_type) != 1");
using Iter = typename Container::iterator;
return details::dump_info<Iter>(std::begin(container), std::end(container), size_per_line);
}
#endif
// create dump_info from ranges
template <typename It>
inline details::dump_info<It> to_hex(const It range_begin,
const It range_end,
size_t size_per_line = 32) {
return details::dump_info<It>(range_begin, range_end, size_per_line);
}
} // namespace spdlog
namespace
#ifdef SPDLOG_USE_STD_FORMAT
std
#else
fmt
#endif
{
template <typename T>
struct formatter<spdlog::details::dump_info<T>, char> {
char delimiter = ' ';
bool put_newlines = true;
bool put_delimiters = true;
bool use_uppercase = false;
bool put_positions = true; // position on start of each line
bool show_ascii = false;
// parse the format string flags
template <typename ParseContext>
SPDLOG_CONSTEXPR_FUNC auto parse(ParseContext &ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin();
while (it != ctx.end() && *it != '}') {
switch (*it) {
case 'X':
use_uppercase = true;
break;
case 's':
put_delimiters = false;
break;
case 'p':
put_positions = false;
break;
case 'n':
put_newlines = false;
show_ascii = false;
break;
case 'a':
if (put_newlines) {
show_ascii = true;
}
break;
}
++it;
}
return it;
}
// format the given bytes range as hex
template <typename FormatContext, typename Container>
auto format(const spdlog::details::dump_info<Container> &the_range,
FormatContext &ctx) const -> decltype(ctx.out()) {
SPDLOG_CONSTEXPR const char *hex_upper = "0123456789ABCDEF";
SPDLOG_CONSTEXPR const char *hex_lower = "0123456789abcdef";
const char *hex_chars = use_uppercase ? hex_upper : hex_lower;
#if !defined(SPDLOG_USE_STD_FORMAT) && FMT_VERSION < 60000
auto inserter = ctx.begin();
#else
auto inserter = ctx.out();
#endif
int size_per_line = static_cast<int>(the_range.size_per_line());
auto start_of_line = the_range.get_begin();
for (auto i = the_range.get_begin(); i != the_range.get_end(); i++) {
auto ch = static_cast<unsigned char>(*i);
if (put_newlines &&
(i == the_range.get_begin() || i - start_of_line >= size_per_line)) {
if (show_ascii && i != the_range.get_begin()) {
*inserter++ = delimiter;
*inserter++ = delimiter;
for (auto j = start_of_line; j < i; j++) {
auto pc = static_cast<unsigned char>(*j);
*inserter++ = std::isprint(pc) ? static_cast<char>(*j) : '.';
}
}
put_newline(inserter, static_cast<size_t>(i - the_range.get_begin()));
// put first byte without delimiter in front of it
*inserter++ = hex_chars[(ch >> 4) & 0x0f];
*inserter++ = hex_chars[ch & 0x0f];
start_of_line = i;
continue;
}
if (put_delimiters && i != the_range.get_begin()) {
*inserter++ = delimiter;
}
*inserter++ = hex_chars[(ch >> 4) & 0x0f];
*inserter++ = hex_chars[ch & 0x0f];
}
if (show_ascii) // add ascii to last line
{
if (the_range.get_end() - the_range.get_begin() > size_per_line) {
auto blank_num = size_per_line - (the_range.get_end() - start_of_line);
while (blank_num-- > 0) {
*inserter++ = delimiter;
*inserter++ = delimiter;
if (put_delimiters) {
*inserter++ = delimiter;
}
}
}
*inserter++ = delimiter;
*inserter++ = delimiter;
for (auto j = start_of_line; j != the_range.get_end(); j++) {
auto pc = static_cast<unsigned char>(*j);
*inserter++ = std::isprint(pc) ? static_cast<char>(*j) : '.';
}
}
return inserter;
}
// put newline(and position header)
template <typename It>
void put_newline(It inserter, std::size_t pos) const {
#ifdef _WIN32
*inserter++ = '\r';
#endif
*inserter++ = '\n';
if (put_positions) {
spdlog::fmt_lib::format_to(inserter, SPDLOG_FMT_STRING("{:04X}: "), pos);
}
}
};
} // namespace std
+220
View File
@@ -0,0 +1,220 @@
// Formatting library for C++ - dynamic argument lists
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_ARGS_H_
#define FMT_ARGS_H_
#ifndef FMT_MODULE
# include <functional> // std::reference_wrapper
# include <memory> // std::unique_ptr
# include <vector>
#endif
#include "format.h" // std_string_view
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename T> struct is_reference_wrapper : std::false_type {};
template <typename T>
struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
template <typename T> auto unwrap(const T& v) -> const T& { return v; }
template <typename T>
auto unwrap(const std::reference_wrapper<T>& v) -> const T& {
return static_cast<const T&>(v);
}
// node is defined outside dynamic_arg_list to workaround a C2504 bug in MSVC
// 2022 (v17.10.0).
//
// Workaround for clang's -Wweak-vtables. Unlike for regular classes, for
// templates it doesn't complain about inability to deduce single translation
// unit for placing vtable. So node is made a fake template.
template <typename = void> struct node {
virtual ~node() = default;
std::unique_ptr<node<>> next;
};
class dynamic_arg_list {
template <typename T> struct typed_node : node<> {
T value;
template <typename Arg>
FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {}
template <typename Char>
FMT_CONSTEXPR typed_node(const basic_string_view<Char>& arg)
: value(arg.data(), arg.size()) {}
};
std::unique_ptr<node<>> head_;
public:
template <typename T, typename Arg> auto push(const Arg& arg) -> const T& {
auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg));
auto& value = new_node->value;
new_node->next = std::move(head_);
head_ = std::move(new_node);
return value;
}
};
} // namespace detail
/**
* A dynamic list of formatting arguments with storage.
*
* It can be implicitly converted into `fmt::basic_format_args` for passing
* into type-erased formatting functions such as `fmt::vformat`.
*/
FMT_EXPORT template <typename Context> class dynamic_format_arg_store {
private:
using char_type = typename Context::char_type;
template <typename T> struct need_copy {
static constexpr detail::type mapped_type =
detail::mapped_type_constant<T, char_type>::value;
enum {
value = !(detail::is_reference_wrapper<T>::value ||
std::is_same<T, basic_string_view<char_type>>::value ||
std::is_same<T, detail::std_string_view<char_type>>::value ||
(mapped_type != detail::type::cstring_type &&
mapped_type != detail::type::string_type &&
mapped_type != detail::type::custom_type))
};
};
template <typename T>
using stored_t = conditional_t<
std::is_convertible<T, std::basic_string<char_type>>::value &&
!detail::is_reference_wrapper<T>::value,
std::basic_string<char_type>, T>;
// Storage of basic_format_arg must be contiguous.
std::vector<basic_format_arg<Context>> data_;
std::vector<detail::named_arg_info<char_type>> named_info_;
// Storage of arguments not fitting into basic_format_arg must grow
// without relocation because items in data_ refer to it.
detail::dynamic_arg_list dynamic_args_;
friend class basic_format_args<Context>;
auto data() const -> const basic_format_arg<Context>* {
return named_info_.empty() ? data_.data() : data_.data() + 1;
}
template <typename T> void emplace_arg(const T& arg) {
data_.emplace_back(arg);
}
template <typename T>
void emplace_arg(const detail::named_arg<char_type, T>& arg) {
if (named_info_.empty())
data_.insert(data_.begin(), basic_format_arg<Context>(nullptr, 0));
data_.emplace_back(detail::unwrap(arg.value));
auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
data->pop_back();
};
std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
guard{&data_, pop_one};
named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
data_[0] = {named_info_.data(), named_info_.size()};
guard.release();
}
public:
constexpr dynamic_format_arg_store() = default;
operator basic_format_args<Context>() const {
return basic_format_args<Context>(data(), static_cast<int>(data_.size()),
!named_info_.empty());
}
/**
* Adds an argument into the dynamic store for later passing to a formatting
* function.
*
* Note that custom types and string types (but not string views) are copied
* into the store dynamically allocating memory if necessary.
*
* **Example**:
*
* fmt::dynamic_format_arg_store<fmt::format_context> store;
* store.push_back(42);
* store.push_back("abc");
* store.push_back(1.5f);
* std::string result = fmt::vformat("{} and {} and {}", store);
*/
template <typename T> void push_back(const T& arg) {
if (detail::const_check(need_copy<T>::value))
emplace_arg(dynamic_args_.push<stored_t<T>>(arg));
else
emplace_arg(detail::unwrap(arg));
}
/**
* Adds a reference to the argument into the dynamic store for later passing
* to a formatting function.
*
* **Example**:
*
* fmt::dynamic_format_arg_store<fmt::format_context> store;
* char band[] = "Rolling Stones";
* store.push_back(std::cref(band));
* band[9] = 'c'; // Changing str affects the output.
* std::string result = fmt::vformat("{}", store);
* // result == "Rolling Scones"
*/
template <typename T> void push_back(std::reference_wrapper<T> arg) {
static_assert(
need_copy<T>::value,
"objects of built-in types and string views are always copied");
emplace_arg(arg.get());
}
/**
* Adds named argument into the dynamic store for later passing to a
* formatting function. `std::reference_wrapper` is supported to avoid
* copying of the argument. The name is always copied into the store.
*/
template <typename T>
void push_back(const detail::named_arg<char_type, T>& arg) {
const char_type* arg_name =
dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
if (detail::const_check(need_copy<T>::value)) {
emplace_arg(
fmt::arg(arg_name, dynamic_args_.push<stored_t<T>>(arg.value)));
} else {
emplace_arg(fmt::arg(arg_name, arg.value));
}
}
/// Erase all elements from the store.
void clear() {
data_.clear();
named_info_.clear();
dynamic_args_ = {};
}
/// Reserves space to store at least `new_cap` arguments including
/// `new_cap_named` named arguments.
void reserve(size_t new_cap, size_t new_cap_named) {
FMT_ASSERT(new_cap >= new_cap_named,
"set of arguments includes set of named arguments");
data_.reserve(new_cap);
named_info_.reserve(new_cap_named);
}
/// Returns the number of elements in the store.
auto size() const noexcept -> size_t { return data_.size(); }
};
FMT_END_NAMESPACE
#endif // FMT_ARGS_H_
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+637
View File
@@ -0,0 +1,637 @@
// Formatting library for C++ - color support
//
// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_COLOR_H_
#define FMT_COLOR_H_
#include "format.h"
FMT_BEGIN_NAMESPACE
FMT_BEGIN_EXPORT
enum class color : uint32_t {
alice_blue = 0xF0F8FF, // rgb(240,248,255)
antique_white = 0xFAEBD7, // rgb(250,235,215)
aqua = 0x00FFFF, // rgb(0,255,255)
aquamarine = 0x7FFFD4, // rgb(127,255,212)
azure = 0xF0FFFF, // rgb(240,255,255)
beige = 0xF5F5DC, // rgb(245,245,220)
bisque = 0xFFE4C4, // rgb(255,228,196)
black = 0x000000, // rgb(0,0,0)
blanched_almond = 0xFFEBCD, // rgb(255,235,205)
blue = 0x0000FF, // rgb(0,0,255)
blue_violet = 0x8A2BE2, // rgb(138,43,226)
brown = 0xA52A2A, // rgb(165,42,42)
burly_wood = 0xDEB887, // rgb(222,184,135)
cadet_blue = 0x5F9EA0, // rgb(95,158,160)
chartreuse = 0x7FFF00, // rgb(127,255,0)
chocolate = 0xD2691E, // rgb(210,105,30)
coral = 0xFF7F50, // rgb(255,127,80)
cornflower_blue = 0x6495ED, // rgb(100,149,237)
cornsilk = 0xFFF8DC, // rgb(255,248,220)
crimson = 0xDC143C, // rgb(220,20,60)
cyan = 0x00FFFF, // rgb(0,255,255)
dark_blue = 0x00008B, // rgb(0,0,139)
dark_cyan = 0x008B8B, // rgb(0,139,139)
dark_golden_rod = 0xB8860B, // rgb(184,134,11)
dark_gray = 0xA9A9A9, // rgb(169,169,169)
dark_green = 0x006400, // rgb(0,100,0)
dark_khaki = 0xBDB76B, // rgb(189,183,107)
dark_magenta = 0x8B008B, // rgb(139,0,139)
dark_olive_green = 0x556B2F, // rgb(85,107,47)
dark_orange = 0xFF8C00, // rgb(255,140,0)
dark_orchid = 0x9932CC, // rgb(153,50,204)
dark_red = 0x8B0000, // rgb(139,0,0)
dark_salmon = 0xE9967A, // rgb(233,150,122)
dark_sea_green = 0x8FBC8F, // rgb(143,188,143)
dark_slate_blue = 0x483D8B, // rgb(72,61,139)
dark_slate_gray = 0x2F4F4F, // rgb(47,79,79)
dark_turquoise = 0x00CED1, // rgb(0,206,209)
dark_violet = 0x9400D3, // rgb(148,0,211)
deep_pink = 0xFF1493, // rgb(255,20,147)
deep_sky_blue = 0x00BFFF, // rgb(0,191,255)
dim_gray = 0x696969, // rgb(105,105,105)
dodger_blue = 0x1E90FF, // rgb(30,144,255)
fire_brick = 0xB22222, // rgb(178,34,34)
floral_white = 0xFFFAF0, // rgb(255,250,240)
forest_green = 0x228B22, // rgb(34,139,34)
fuchsia = 0xFF00FF, // rgb(255,0,255)
gainsboro = 0xDCDCDC, // rgb(220,220,220)
ghost_white = 0xF8F8FF, // rgb(248,248,255)
gold = 0xFFD700, // rgb(255,215,0)
golden_rod = 0xDAA520, // rgb(218,165,32)
gray = 0x808080, // rgb(128,128,128)
green = 0x008000, // rgb(0,128,0)
green_yellow = 0xADFF2F, // rgb(173,255,47)
honey_dew = 0xF0FFF0, // rgb(240,255,240)
hot_pink = 0xFF69B4, // rgb(255,105,180)
indian_red = 0xCD5C5C, // rgb(205,92,92)
indigo = 0x4B0082, // rgb(75,0,130)
ivory = 0xFFFFF0, // rgb(255,255,240)
khaki = 0xF0E68C, // rgb(240,230,140)
lavender = 0xE6E6FA, // rgb(230,230,250)
lavender_blush = 0xFFF0F5, // rgb(255,240,245)
lawn_green = 0x7CFC00, // rgb(124,252,0)
lemon_chiffon = 0xFFFACD, // rgb(255,250,205)
light_blue = 0xADD8E6, // rgb(173,216,230)
light_coral = 0xF08080, // rgb(240,128,128)
light_cyan = 0xE0FFFF, // rgb(224,255,255)
light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210)
light_gray = 0xD3D3D3, // rgb(211,211,211)
light_green = 0x90EE90, // rgb(144,238,144)
light_pink = 0xFFB6C1, // rgb(255,182,193)
light_salmon = 0xFFA07A, // rgb(255,160,122)
light_sea_green = 0x20B2AA, // rgb(32,178,170)
light_sky_blue = 0x87CEFA, // rgb(135,206,250)
light_slate_gray = 0x778899, // rgb(119,136,153)
light_steel_blue = 0xB0C4DE, // rgb(176,196,222)
light_yellow = 0xFFFFE0, // rgb(255,255,224)
lime = 0x00FF00, // rgb(0,255,0)
lime_green = 0x32CD32, // rgb(50,205,50)
linen = 0xFAF0E6, // rgb(250,240,230)
magenta = 0xFF00FF, // rgb(255,0,255)
maroon = 0x800000, // rgb(128,0,0)
medium_aquamarine = 0x66CDAA, // rgb(102,205,170)
medium_blue = 0x0000CD, // rgb(0,0,205)
medium_orchid = 0xBA55D3, // rgb(186,85,211)
medium_purple = 0x9370DB, // rgb(147,112,219)
medium_sea_green = 0x3CB371, // rgb(60,179,113)
medium_slate_blue = 0x7B68EE, // rgb(123,104,238)
medium_spring_green = 0x00FA9A, // rgb(0,250,154)
medium_turquoise = 0x48D1CC, // rgb(72,209,204)
medium_violet_red = 0xC71585, // rgb(199,21,133)
midnight_blue = 0x191970, // rgb(25,25,112)
mint_cream = 0xF5FFFA, // rgb(245,255,250)
misty_rose = 0xFFE4E1, // rgb(255,228,225)
moccasin = 0xFFE4B5, // rgb(255,228,181)
navajo_white = 0xFFDEAD, // rgb(255,222,173)
navy = 0x000080, // rgb(0,0,128)
old_lace = 0xFDF5E6, // rgb(253,245,230)
olive = 0x808000, // rgb(128,128,0)
olive_drab = 0x6B8E23, // rgb(107,142,35)
orange = 0xFFA500, // rgb(255,165,0)
orange_red = 0xFF4500, // rgb(255,69,0)
orchid = 0xDA70D6, // rgb(218,112,214)
pale_golden_rod = 0xEEE8AA, // rgb(238,232,170)
pale_green = 0x98FB98, // rgb(152,251,152)
pale_turquoise = 0xAFEEEE, // rgb(175,238,238)
pale_violet_red = 0xDB7093, // rgb(219,112,147)
papaya_whip = 0xFFEFD5, // rgb(255,239,213)
peach_puff = 0xFFDAB9, // rgb(255,218,185)
peru = 0xCD853F, // rgb(205,133,63)
pink = 0xFFC0CB, // rgb(255,192,203)
plum = 0xDDA0DD, // rgb(221,160,221)
powder_blue = 0xB0E0E6, // rgb(176,224,230)
purple = 0x800080, // rgb(128,0,128)
rebecca_purple = 0x663399, // rgb(102,51,153)
red = 0xFF0000, // rgb(255,0,0)
rosy_brown = 0xBC8F8F, // rgb(188,143,143)
royal_blue = 0x4169E1, // rgb(65,105,225)
saddle_brown = 0x8B4513, // rgb(139,69,19)
salmon = 0xFA8072, // rgb(250,128,114)
sandy_brown = 0xF4A460, // rgb(244,164,96)
sea_green = 0x2E8B57, // rgb(46,139,87)
sea_shell = 0xFFF5EE, // rgb(255,245,238)
sienna = 0xA0522D, // rgb(160,82,45)
silver = 0xC0C0C0, // rgb(192,192,192)
sky_blue = 0x87CEEB, // rgb(135,206,235)
slate_blue = 0x6A5ACD, // rgb(106,90,205)
slate_gray = 0x708090, // rgb(112,128,144)
snow = 0xFFFAFA, // rgb(255,250,250)
spring_green = 0x00FF7F, // rgb(0,255,127)
steel_blue = 0x4682B4, // rgb(70,130,180)
tan = 0xD2B48C, // rgb(210,180,140)
teal = 0x008080, // rgb(0,128,128)
thistle = 0xD8BFD8, // rgb(216,191,216)
tomato = 0xFF6347, // rgb(255,99,71)
turquoise = 0x40E0D0, // rgb(64,224,208)
violet = 0xEE82EE, // rgb(238,130,238)
wheat = 0xF5DEB3, // rgb(245,222,179)
white = 0xFFFFFF, // rgb(255,255,255)
white_smoke = 0xF5F5F5, // rgb(245,245,245)
yellow = 0xFFFF00, // rgb(255,255,0)
yellow_green = 0x9ACD32 // rgb(154,205,50)
}; // enum class color
enum class terminal_color : uint8_t {
black = 30,
red,
green,
yellow,
blue,
magenta,
cyan,
white,
bright_black = 90,
bright_red,
bright_green,
bright_yellow,
bright_blue,
bright_magenta,
bright_cyan,
bright_white
};
enum class emphasis : uint8_t {
bold = 1,
faint = 1 << 1,
italic = 1 << 2,
underline = 1 << 3,
blink = 1 << 4,
reverse = 1 << 5,
conceal = 1 << 6,
strikethrough = 1 << 7,
};
// rgb is a struct for red, green and blue colors.
// Using the name "rgb" makes some editors show the color in a tooltip.
struct rgb {
constexpr rgb() : r(0), g(0), b(0) {}
constexpr rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
constexpr rgb(uint32_t hex)
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
constexpr rgb(color hex)
: r((uint32_t(hex) >> 16) & 0xFF),
g((uint32_t(hex) >> 8) & 0xFF),
b(uint32_t(hex) & 0xFF) {}
uint8_t r;
uint8_t g;
uint8_t b;
};
namespace detail {
// A bit-packed variant of an RGB color, a terminal color, or unset color.
// see text_style for the bit-packing scheme.
struct color_type {
constexpr color_type() noexcept = default;
constexpr color_type(color rgb_color) noexcept
: value_(static_cast<uint32_t>(rgb_color) | (1 << 24)) {}
constexpr color_type(rgb rgb_color) noexcept
: color_type(static_cast<color>(
(static_cast<uint32_t>(rgb_color.r) << 16) |
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b)) {}
constexpr color_type(terminal_color term_color) noexcept
: value_(static_cast<uint32_t>(term_color) | (3 << 24)) {}
constexpr auto is_terminal_color() const noexcept -> bool {
return (value_ & (1 << 25)) != 0;
}
constexpr auto value() const noexcept -> uint32_t {
return value_ & 0xFFFFFF;
}
constexpr color_type(uint32_t value) noexcept : value_(value) {}
uint32_t value_ = 0;
};
} // namespace detail
/// A text style consisting of foreground and background colors and emphasis.
class text_style {
// The information is packed as follows:
// ┌──┐
// │ 0│─┐
// │..│ ├── foreground color value
// │23│─┘
// ├──┤
// │24│─┬── discriminator for the above value. 00 if unset, 01 if it's
// │25│─┘ an RGB color, or 11 if it's a terminal color (10 is unused)
// ├──┤
// │26│──── overflow bit, always zero (see below)
// ├──┤
// │27│─┐
// │..│ │
// │50│ │
// ├──┤ │
// │51│ ├── background color (same format as the foreground color)
// │52│ │
// ├──┤ │
// │53│─┘
// ├──┤
// │54│─┐
// │..│ ├── emphases
// │61│─┘
// ├──┤
// │62│─┬── unused
// │63│─┘
// └──┘
// The overflow bits are there to make operator|= efficient.
// When ORing, we must throw if, for either the foreground or background,
// one style specifies a terminal color and the other specifies any color
// (terminal or RGB); in other words, if one discriminator is 11 and the
// other is 11 or 01.
//
// We do that check by adding the styles. Consider what adding does to each
// possible pair of discriminators:
// 00 + 00 = 000
// 01 + 00 = 001
// 11 + 00 = 011
// 01 + 01 = 010
// 11 + 01 = 100 (!!)
// 11 + 11 = 110 (!!)
// In the last two cases, the ones we want to catch, the third bit——the
// overflow bit——is set. Bingo.
//
// We must take into account the possible carry bit from the bits
// before the discriminator. The only potentially problematic case is
// 11 + 00 = 011 (a carry bit would make it 100, not good!), but a carry
// bit is impossible in that case, because 00 (unset color) means the
// 24 bits that precede the discriminator are all zero.
//
// This test can be applied to both colors simultaneously.
public:
FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept
: style_(static_cast<uint64_t>(em) << 54) {}
FMT_CONSTEXPR auto operator|=(text_style rhs) -> text_style& {
if (((style_ + rhs.style_) & ((1ULL << 26) | (1ULL << 53))) != 0)
report_error("can't OR a terminal color");
style_ |= rhs.style_;
return *this;
}
friend FMT_CONSTEXPR auto operator|(text_style lhs, text_style rhs)
-> text_style {
return lhs |= rhs;
}
FMT_CONSTEXPR auto operator==(text_style rhs) const noexcept -> bool {
return style_ == rhs.style_;
}
FMT_CONSTEXPR auto operator!=(text_style rhs) const noexcept -> bool {
return !(*this == rhs);
}
FMT_CONSTEXPR auto has_foreground() const noexcept -> bool {
return (style_ & (1 << 24)) != 0;
}
FMT_CONSTEXPR auto has_background() const noexcept -> bool {
return (style_ & (1ULL << 51)) != 0;
}
FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool {
return (style_ >> 54) != 0;
}
FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type {
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
return style_ & 0x3FFFFFF;
}
FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type {
FMT_ASSERT(has_background(), "no background specified for this style");
return (style_ >> 27) & 0x3FFFFFF;
}
FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis {
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
return static_cast<emphasis>(style_ >> 54);
}
private:
FMT_CONSTEXPR text_style(uint64_t style) noexcept : style_(style) {}
friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept
-> text_style;
friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept
-> text_style;
uint64_t style_ = 0;
};
/// Creates a text style from the foreground (text) color.
FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept
-> text_style {
return foreground.value_;
}
/// Creates a text style from the background color.
FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept
-> text_style {
return static_cast<uint64_t>(background.value_) << 27;
}
FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
-> text_style {
return text_style(lhs) | rhs;
}
namespace detail {
template <typename Char> struct ansi_color_escape {
FMT_CONSTEXPR ansi_color_escape(color_type text_color,
const char* esc) noexcept {
// If we have a terminal color, we need to output another escape code
// sequence.
if (text_color.is_terminal_color()) {
bool is_background = esc == string_view("\x1b[48;2;");
uint32_t value = text_color.value();
// Background ASCII codes are the same as the foreground ones but with
// 10 more.
if (is_background) value += 10u;
buffer[size++] = static_cast<Char>('\x1b');
buffer[size++] = static_cast<Char>('[');
if (value >= 100u) {
buffer[size++] = static_cast<Char>('1');
value %= 100u;
}
buffer[size++] = static_cast<Char>('0' + value / 10u);
buffer[size++] = static_cast<Char>('0' + value % 10u);
buffer[size++] = static_cast<Char>('m');
return;
}
for (int i = 0; i < 7; i++) {
buffer[i] = static_cast<Char>(esc[i]);
}
rgb color(text_color.value());
to_esc(color.r, buffer + 7, ';');
to_esc(color.g, buffer + 11, ';');
to_esc(color.b, buffer + 15, 'm');
size = 19;
}
FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept {
uint8_t em_codes[num_emphases] = {};
if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1;
if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2;
if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3;
if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4;
if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5;
if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7;
if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8;
if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9;
buffer[size++] = static_cast<Char>('\x1b');
buffer[size++] = static_cast<Char>('[');
for (size_t i = 0; i < num_emphases; ++i) {
if (!em_codes[i]) continue;
buffer[size++] = static_cast<Char>('0' + em_codes[i]);
buffer[size++] = static_cast<Char>(';');
}
buffer[size - 1] = static_cast<Char>('m');
}
FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; }
FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; }
FMT_CONSTEXPR auto end() const noexcept -> const Char* {
return buffer + size;
}
private:
static constexpr size_t num_emphases = 8;
Char buffer[7u + 4u * num_emphases] = {};
size_t size = 0;
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
char delimiter) noexcept {
out[0] = static_cast<Char>('0' + c / 100);
out[1] = static_cast<Char>('0' + c / 10 % 10);
out[2] = static_cast<Char>('0' + c % 10);
out[3] = static_cast<Char>(delimiter);
}
static FMT_CONSTEXPR auto has_emphasis(emphasis em, emphasis mask) noexcept
-> bool {
return static_cast<uint8_t>(em) & static_cast<uint8_t>(mask);
}
};
template <typename Char>
FMT_CONSTEXPR auto make_foreground_color(color_type foreground) noexcept
-> ansi_color_escape<Char> {
return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
}
template <typename Char>
FMT_CONSTEXPR auto make_background_color(color_type background) noexcept
-> ansi_color_escape<Char> {
return ansi_color_escape<Char>(background, "\x1b[48;2;");
}
template <typename Char>
FMT_CONSTEXPR auto make_emphasis(emphasis em) noexcept
-> ansi_color_escape<Char> {
return ansi_color_escape<Char>(em);
}
template <typename Char> inline void reset_color(buffer<Char>& buffer) {
auto reset_color = string_view("\x1b[0m");
buffer.append(reset_color.begin(), reset_color.end());
}
template <typename T> struct styled_arg : view {
const T& value;
text_style style;
styled_arg(const T& v, text_style s) : value(v), style(s) {}
};
template <typename Char>
void vformat_to(buffer<Char>& buf, text_style ts, basic_string_view<Char> fmt,
basic_format_args<buffered_context<Char>> args) {
if (ts.has_emphasis()) {
auto emphasis = make_emphasis<Char>(ts.get_emphasis());
buf.append(emphasis.begin(), emphasis.end());
}
if (ts.has_foreground()) {
auto foreground = make_foreground_color<Char>(ts.get_foreground());
buf.append(foreground.begin(), foreground.end());
}
if (ts.has_background()) {
auto background = make_background_color<Char>(ts.get_background());
buf.append(background.begin(), background.end());
}
vformat_to(buf, fmt, args);
if (ts != text_style()) reset_color<Char>(buf);
}
} // namespace detail
inline void vprint(FILE* f, text_style ts, string_view fmt, format_args args) {
auto buf = memory_buffer();
detail::vformat_to(buf, ts, fmt, args);
print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size()));
}
/**
* Formats a string and prints it to the specified file stream using ANSI
* escape sequences to specify text formatting.
*
* **Example**:
*
* fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
* "Elapsed time: {0:.2f} seconds", 1.23);
*/
template <typename... T>
void print(FILE* f, text_style ts, format_string<T...> fmt, T&&... args) {
vprint(f, ts, fmt.str, vargs<T...>{{args...}});
}
/**
* Formats a string and prints it to stdout using ANSI escape sequences to
* specify text formatting.
*
* **Example**:
*
* fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
* "Elapsed time: {0:.2f} seconds", 1.23);
*/
template <typename... T>
void print(text_style ts, format_string<T...> fmt, T&&... args) {
return print(stdout, ts, fmt, std::forward<T>(args)...);
}
inline auto vformat(text_style ts, string_view fmt, format_args args)
-> std::string {
auto buf = memory_buffer();
detail::vformat_to(buf, ts, fmt, args);
return fmt::to_string(buf);
}
/**
* Formats arguments and returns the result as a string using ANSI escape
* sequences to specify text formatting.
*
* **Example**:
*
* ```
* #include <fmt/color.h>
* std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red),
* "The answer is {}", 42);
* ```
*/
template <typename... T>
inline auto format(text_style ts, format_string<T...> fmt, T&&... args)
-> std::string {
return fmt::vformat(ts, fmt.str, vargs<T...>{{args...}});
}
/// Formats a string with the given text_style and writes the output to `out`.
template <typename OutputIt,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
auto vformat_to(OutputIt out, text_style ts, string_view fmt, format_args args)
-> OutputIt {
auto&& buf = detail::get_buffer<char>(out);
detail::vformat_to(buf, ts, fmt, args);
return detail::get_iterator(buf, out);
}
/**
* Formats arguments with the given text style, writes the result to the output
* iterator `out` and returns the iterator past the end of the output range.
*
* **Example**:
*
* std::vector<char> out;
* fmt::format_to(std::back_inserter(out),
* fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
*/
template <typename OutputIt, typename... T,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
inline auto format_to(OutputIt out, text_style ts, format_string<T...> fmt,
T&&... args) -> OutputIt {
return vformat_to(out, ts, fmt.str, vargs<T...>{{args...}});
}
template <typename T, typename Char>
struct formatter<detail::styled_arg<T>, Char> : formatter<T, Char> {
template <typename FormatContext>
auto format(const detail::styled_arg<T>& arg, FormatContext& ctx) const
-> decltype(ctx.out()) {
const auto& ts = arg.style;
auto out = ctx.out();
bool has_style = false;
if (ts.has_emphasis()) {
has_style = true;
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
out = detail::copy<Char>(emphasis.begin(), emphasis.end(), out);
}
if (ts.has_foreground()) {
has_style = true;
auto foreground =
detail::make_foreground_color<Char>(ts.get_foreground());
out = detail::copy<Char>(foreground.begin(), foreground.end(), out);
}
if (ts.has_background()) {
has_style = true;
auto background =
detail::make_background_color<Char>(ts.get_background());
out = detail::copy<Char>(background.begin(), background.end(), out);
}
out = formatter<T, Char>::format(arg.value, ctx);
if (has_style) {
auto reset_color = string_view("\x1b[0m");
out = detail::copy<Char>(reset_color.begin(), reset_color.end(), out);
}
return out;
}
};
/**
* Returns an argument that will be formatted using ANSI escape sequences,
* to be used in a formatting function.
*
* **Example**:
*
* fmt::print("Elapsed time: {0:.2f} seconds",
* fmt::styled(1.23, fmt::fg(fmt::color::green) |
* fmt::bg(fmt::color::blue)));
*/
template <typename T>
FMT_CONSTEXPR auto styled(const T& value, text_style ts)
-> detail::styled_arg<remove_cvref_t<T>> {
return detail::styled_arg<remove_cvref_t<T>>{value, ts};
}
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_COLOR_H_
+588
View File
@@ -0,0 +1,588 @@
// Formatting library for C++ - experimental format string compilation
//
// Copyright (c) 2012 - present, Victor Zverovich and fmt contributors
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_COMPILE_H_
#define FMT_COMPILE_H_
#ifndef FMT_MODULE
# include <iterator> // std::back_inserter
#endif
#include "format.h"
FMT_BEGIN_NAMESPACE
FMT_BEGIN_EXPORT
// A compile-time string which is compiled into fast formatting code.
class compiled_string {};
template <typename S>
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
/**
* Converts a string literal `s` into a format string that will be parsed at
* compile time and converted into efficient formatting code. Requires C++17
* `constexpr if` compiler support.
*
* **Example**:
*
* // Converts 42 into std::string using the most efficient method and no
* // runtime format string processing.
* std::string s = fmt::format(FMT_COMPILE("{}"), 42);
*/
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
# define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string)
#else
# define FMT_COMPILE(s) FMT_STRING(s)
#endif
/**
* Converts a string literal into a format string that will be parsed at
* compile time and converted into efficient formatting code. Requires support
* for class types in constant template parameters (a C++20 feature).
*
* **Example**:
*
* // Converts 42 into std::string using the most efficient method and no
* // runtime format string processing.
* using namespace fmt::literals;
* std::string s = fmt::format("{}"_cf, 42);
*/
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
inline namespace literals {
template <detail::fixed_string Str> constexpr auto operator""_cf() {
return FMT_COMPILE(Str.data);
}
} // namespace literals
#endif
FMT_END_EXPORT
namespace detail {
template <typename T, typename... Tail>
constexpr auto first(const T& value, const Tail&...) -> const T& {
return value;
}
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename... T> struct type_list {};
// Returns a reference to the argument at index N from [first, rest...].
template <int N, typename T, typename... Args>
constexpr auto get([[maybe_unused]] const T& first,
[[maybe_unused]] const Args&... rest) -> const auto& {
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
if constexpr (N == 0)
return first;
else
return detail::get<N - 1>(rest...);
}
# if FMT_USE_NONTYPE_TEMPLATE_ARGS
template <int N, typename T, typename... Args, typename Char>
constexpr auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
if constexpr (is_static_named_arg<T>()) {
if (name == T::name) return N;
}
if constexpr (sizeof...(Args) > 0)
return get_arg_index_by_name<N + 1, Args...>(name);
(void)name; // Workaround an MSVC bug about "unused" parameter.
return -1;
}
# endif
template <typename... Args, typename Char>
FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
# if FMT_USE_NONTYPE_TEMPLATE_ARGS
if constexpr (sizeof...(Args) > 0)
return get_arg_index_by_name<0, Args...>(name);
# endif
(void)name;
return -1;
}
template <typename Char, typename... Args>
constexpr auto get_arg_index_by_name(basic_string_view<Char> name,
type_list<Args...>) -> int {
return get_arg_index_by_name<Args...>(name);
}
template <int N, typename> struct get_type_impl;
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
using type =
remove_cvref_t<decltype(detail::get<N>(std::declval<Args>()...))>;
};
template <int N, typename T>
using get_type = typename get_type_impl<N, T>::type;
template <typename T> struct is_compiled_format : std::false_type {};
template <typename Char> struct text {
basic_string_view<Char> data;
using char_type = Char;
template <typename OutputIt, typename... T>
constexpr auto format(OutputIt out, const T&...) const -> OutputIt {
return write<Char>(out, data);
}
};
template <typename Char>
struct is_compiled_format<text<Char>> : std::true_type {};
template <typename Char>
constexpr auto make_text(basic_string_view<Char> s, size_t pos, size_t size)
-> text<Char> {
return {{&s[pos], size}};
}
template <typename Char> struct code_unit {
Char value;
using char_type = Char;
template <typename OutputIt, typename... T>
constexpr auto format(OutputIt out, const T&...) const -> OutputIt {
*out++ = value;
return out;
}
};
// This ensures that the argument type is convertible to `const T&`.
template <typename T, int N, typename... Args>
constexpr auto get_arg_checked(const Args&... args) -> const T& {
const auto& arg = detail::get<N>(args...);
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
return arg.value;
} else {
return arg;
}
}
template <typename Char>
struct is_compiled_format<code_unit<Char>> : std::true_type {};
// A replacement field that refers to argument N.
template <typename Char, typename V, int N> struct field {
using char_type = Char;
template <typename OutputIt, typename... T>
constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
const V& arg = get_arg_checked<V, N>(args...);
if constexpr (std::is_convertible<V, basic_string_view<Char>>::value) {
auto s = basic_string_view<Char>(arg);
return copy<Char>(s.begin(), s.end(), out);
} else {
return write<Char>(out, arg);
}
}
};
template <typename Char, typename T, int N>
struct is_compiled_format<field<Char, T, N>> : std::true_type {};
// A replacement field that refers to argument with name.
template <typename Char> struct runtime_named_field {
using char_type = Char;
basic_string_view<Char> name;
template <typename OutputIt, typename T>
constexpr static auto try_format_argument(
OutputIt& out,
// [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
[[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) -> bool {
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
if (arg_name == arg.name) {
out = write<Char>(out, arg.value);
return true;
}
}
return false;
}
template <typename OutputIt, typename... T>
constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
bool found = (try_format_argument(out, name, args) || ...);
if (!found) {
FMT_THROW(format_error("argument with specified name is not found"));
}
return out;
}
};
template <typename Char>
struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
// A replacement field that refers to argument N and has format specifiers.
template <typename Char, typename V, int N> struct spec_field {
using char_type = Char;
formatter<V, Char> fmt;
template <typename OutputIt, typename... T>
constexpr FMT_INLINE auto format(OutputIt out, const T&... args) const
-> OutputIt {
const auto& vargs =
fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...);
basic_format_context<OutputIt, Char> ctx(out, vargs);
return fmt.format(get_arg_checked<V, N>(args...), ctx);
}
};
template <typename Char, typename T, int N>
struct is_compiled_format<spec_field<Char, T, N>> : std::true_type {};
template <typename L, typename R> struct concat {
L lhs;
R rhs;
using char_type = typename L::char_type;
template <typename OutputIt, typename... T>
constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
out = lhs.format(out, args...);
return rhs.format(out, args...);
}
};
template <typename L, typename R>
struct is_compiled_format<concat<L, R>> : std::true_type {};
template <typename L, typename R>
constexpr auto make_concat(L lhs, R rhs) -> concat<L, R> {
return {lhs, rhs};
}
struct unknown_format {};
template <typename Char>
constexpr auto parse_text(basic_string_view<Char> str, size_t pos) -> size_t {
for (size_t size = str.size(); pos != size; ++pos) {
if (str[pos] == '{' || str[pos] == '}') break;
}
return pos;
}
template <typename Args, size_t POS, int ID, typename S>
constexpr auto compile_format_string(S fmt);
template <typename Args, size_t POS, int ID, typename T, typename S>
constexpr auto parse_tail(T head, S fmt) {
if constexpr (POS != basic_string_view<typename S::char_type>(fmt).size()) {
constexpr auto tail = compile_format_string<Args, POS, ID>(fmt);
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
unknown_format>())
return tail;
else
return make_concat(head, tail);
} else {
return head;
}
}
template <typename T, typename Char> struct parse_specs_result {
formatter<T, Char> fmt;
size_t end;
int next_arg_id;
};
enum { manual_indexing_id = -1 };
template <typename T, typename Char>
constexpr auto parse_specs(basic_string_view<Char> str, size_t pos,
int next_arg_id) -> parse_specs_result<T, Char> {
str.remove_prefix(pos);
auto ctx =
compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id);
auto f = formatter<T, Char>();
auto end = f.parse(ctx);
return {f, pos + fmt::detail::to_unsigned(end - str.data()),
next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()};
}
template <typename Char> struct arg_id_handler {
arg_id_kind kind;
arg_ref<Char> arg_id;
constexpr auto on_auto() -> int {
FMT_ASSERT(false, "handler cannot be used with automatic indexing");
return 0;
}
constexpr auto on_index(int id) -> int {
kind = arg_id_kind::index;
arg_id = arg_ref<Char>(id);
return 0;
}
constexpr auto on_name(basic_string_view<Char> id) -> int {
kind = arg_id_kind::name;
arg_id = arg_ref<Char>(id);
return 0;
}
};
template <typename Char> struct parse_arg_id_result {
arg_id_kind kind;
arg_ref<Char> arg_id;
const Char* arg_id_end;
};
template <int ID, typename Char>
constexpr auto parse_arg_id(const Char* begin, const Char* end) {
auto handler = arg_id_handler<Char>{arg_id_kind::none, arg_ref<Char>{}};
auto arg_id_end = parse_arg_id(begin, end, handler);
return parse_arg_id_result<Char>{handler.kind, handler.arg_id, arg_id_end};
}
template <typename T, typename Enable = void> struct field_type {
using type = remove_cvref_t<T>;
};
template <typename T>
struct field_type<T, enable_if_t<detail::is_named_arg<T>::value>> {
using type = remove_cvref_t<decltype(T::value)>;
};
template <typename T, typename Args, size_t END_POS, int ARG_INDEX, int NEXT_ID,
typename S>
constexpr auto parse_replacement_field_then_tail(S fmt) {
using char_type = typename S::char_type;
constexpr auto str = basic_string_view<char_type>(fmt);
constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type();
if constexpr (c == '}') {
return parse_tail<Args, END_POS + 1, NEXT_ID>(
field<char_type, typename field_type<T>::type, ARG_INDEX>(), fmt);
} else if constexpr (c != ':') {
FMT_THROW(format_error("expected ':'"));
} else {
constexpr auto result = parse_specs<typename field_type<T>::type>(
str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID);
if constexpr (result.end >= str.size() || str[result.end] != '}') {
FMT_THROW(format_error("expected '}'"));
return 0;
} else {
return parse_tail<Args, result.end + 1, result.next_arg_id>(
spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
result.fmt},
fmt);
}
}
}
// Compiles a non-empty format string and returns the compiled representation
// or unknown_format() on unrecognized input.
template <typename Args, size_t POS, int ID, typename S>
constexpr auto compile_format_string(S fmt) {
using char_type = typename S::char_type;
constexpr auto str = basic_string_view<char_type>(fmt);
if constexpr (str[POS] == '{') {
if constexpr (POS + 1 == str.size())
FMT_THROW(format_error("unmatched '{' in format string"));
if constexpr (str[POS + 1] == '{') {
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), fmt);
} else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {
static_assert(ID != manual_indexing_id,
"cannot switch from manual to automatic argument indexing");
constexpr auto next_id =
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
return parse_replacement_field_then_tail<get_type<ID, Args>, Args,
POS + 1, ID, next_id>(fmt);
} else {
constexpr auto arg_id_result =
parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size());
constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data();
constexpr char_type c =
arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type();
static_assert(c == '}' || c == ':', "missing '}' in format string");
if constexpr (arg_id_result.kind == arg_id_kind::index) {
static_assert(
ID == manual_indexing_id || ID == 0,
"cannot switch from automatic to manual argument indexing");
constexpr auto arg_index = arg_id_result.arg_id.index;
return parse_replacement_field_then_tail<get_type<arg_index, Args>,
Args, arg_id_end_pos,
arg_index, manual_indexing_id>(
fmt);
} else if constexpr (arg_id_result.kind == arg_id_kind::name) {
constexpr auto arg_index =
get_arg_index_by_name(arg_id_result.arg_id.name, Args{});
if constexpr (arg_index >= 0) {
constexpr auto next_id =
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
return parse_replacement_field_then_tail<
decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
arg_index, next_id>(fmt);
} else if constexpr (c == '}') {
return parse_tail<Args, arg_id_end_pos + 1, ID>(
runtime_named_field<char_type>{arg_id_result.arg_id.name}, fmt);
} else if constexpr (c == ':') {
return unknown_format(); // no type info for specs parsing
}
}
}
} else if constexpr (str[POS] == '}') {
if constexpr (POS + 1 == str.size())
FMT_THROW(format_error("unmatched '}' in format string"));
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), fmt);
} else {
constexpr auto end = parse_text(str, POS + 1);
if constexpr (end - POS > 1) {
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS), fmt);
} else {
return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]}, fmt);
}
}
}
template <typename... Args, typename S,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
constexpr auto compile(S fmt) {
constexpr auto str = basic_string_view<typename S::char_type>(fmt);
if constexpr (str.size() == 0) {
return detail::make_text(str, 0, 0);
} else {
constexpr auto result =
detail::compile_format_string<detail::type_list<Args...>, 0, 0>(fmt);
return result;
}
}
#endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
} // namespace detail
FMT_BEGIN_EXPORT
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename CompiledFormat, typename... T,
typename Char = typename CompiledFormat::char_type,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
FMT_INLINE FMT_CONSTEXPR_STRING auto format(const CompiledFormat& cf,
const T&... args)
-> std::basic_string<Char> {
auto s = std::basic_string<Char>();
cf.format(std::back_inserter(s), args...);
return s;
}
template <typename OutputIt, typename CompiledFormat, typename... T,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
constexpr FMT_INLINE auto format_to(OutputIt out, const CompiledFormat& cf,
const T&... args) -> OutputIt {
return cf.format(out, args...);
}
template <typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_INLINE FMT_CONSTEXPR_STRING auto format(const S&, T&&... args)
-> std::basic_string<typename S::char_type> {
if constexpr (std::is_same<typename S::char_type, char>::value) {
constexpr auto str = basic_string_view<typename S::char_type>(S());
if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {
const auto& first = detail::first(args...);
if constexpr (detail::is_named_arg<
remove_cvref_t<decltype(first)>>::value) {
return fmt::to_string(first.value);
} else {
return fmt::to_string(first);
}
}
}
constexpr auto compiled = detail::compile<T...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) {
return fmt::format(
static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<T>(args)...);
} else {
return fmt::format(compiled, std::forward<T>(args)...);
}
}
template <typename OutputIt, typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_CONSTEXPR auto format_to(OutputIt out, const S&, T&&... args) -> OutputIt {
constexpr auto compiled = detail::compile<T...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) {
return fmt::format_to(
out, static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<T>(args)...);
} else {
return fmt::format_to(out, compiled, std::forward<T>(args)...);
}
}
#endif
template <typename OutputIt, typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
-> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits;
auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
fmt::format_to(std::back_inserter(buf), fmt, std::forward<T>(args)...);
return {buf.out(), buf.count()};
}
template <typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_CONSTEXPR20 auto formatted_size(const S& fmt, T&&... args) -> size_t {
auto buf = detail::counting_buffer<>();
fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
return buf.count();
}
template <typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
void print(std::FILE* f, const S& fmt, T&&... args) {
auto buf = memory_buffer();
fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
detail::print(f, {buf.data(), buf.size()});
}
template <typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
void print(const S& fmt, T&&... args) {
print(stdout, fmt, std::forward<T>(args)...);
}
template <size_t N> class static_format_result {
private:
char data[N];
public:
template <typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
explicit FMT_CONSTEXPR static_format_result(const S& fmt, T&&... args) {
*fmt::format_to(data, fmt, std::forward<T>(args)...) = '\0';
}
auto str() const -> fmt::string_view { return {data, N - 1}; }
auto c_str() const -> const char* { return data; }
};
/**
* Formats arguments according to the format string `fmt_str` and produces
* a string of the exact required size at compile time. Both the format string
* and the arguments must be compile-time expressions.
*
* The resulting string can be accessed as a C string via `c_str()` or as
* a `fmt::string_view` via `str()`.
*
* **Example**:
*
* // Produces the static string "42" at compile time.
* static constexpr auto result = FMT_STATIC_FORMAT("{}", 42);
* const char* s = result.c_str();
*/
#define FMT_STATIC_FORMAT(fmt_str, ...) \
fmt::static_format_result< \
fmt::formatted_size(FMT_COMPILE(fmt_str), __VA_ARGS__) + 1>( \
FMT_COMPILE(fmt_str), __VA_ARGS__)
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_COMPILE_H_
+5
View File
@@ -0,0 +1,5 @@
// This file is only provided for compatibility and may be removed in future
// versions. Use fmt/base.h if you don't need fmt::format and fmt/format.h
// otherwise.
#include "format.h"
@@ -0,0 +1,27 @@
Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- Optional exception to the license ---
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into a machine-executable object form of such
source code, you may redistribute such embedded portions in such object form
without including the above copyright and permission notices.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More