Compare commits
No commits in common. "WeldSeam_1.1.0.3" and "master" have entirely different histories.
WeldSeam_1
...
master
@ -1,33 +0,0 @@
|
||||
# 点云保存与内存保护
|
||||
|
||||
## 自动完整保存
|
||||
|
||||
- 扫描开始时同时开启 `enableDebug` 和 `savePointCloud`,基类为每台相机的本次扫描创建一个文件。
|
||||
- 扫描期间逐帧提交原始点云,后台线程持续写入 TXT;不分段,不等待全部数据进入内存后再保存。
|
||||
- 此自动采集保存路径固定使用 TXT,不受 `storageType` 的二进制选择影响。手动保存和原有离线保存仍沿用原配置。
|
||||
- 输出位置沿用 `debugOutputPath`,按应用名和日期建立目录,文件名包含设备和扫描时间。
|
||||
- 正在写入的文件暂用 `.txt.part` 后缀,内容为 TXT。正常结束时排空待写队列、回填实际扫描线数,再改名为 `.txt`。
|
||||
- 创建文件失败时不启动该相机的扫描;写入失败、取消或断线时保留 `.part`,不能将它视为完整扫描。异常退出留下的 `.part` 需要人工检查和清理。
|
||||
- 每台相机的写入队列上限为 8 MiB,包含正在写入的扫描线。队列满时等待写线程,不静默丢帧;超过队列容量的单帧或写入错误会明确标记失败。
|
||||
- TXT 格式化在后台线程完成,SDK 回调仅复制待写扫描线,不长期保存 SDK 指针。
|
||||
|
||||
## WeldSeam 缓存
|
||||
|
||||
- 不论调试保存是否开启,WeldSeam 都尝试保留 `m_detectionDataCache`,供右键手动保存使用。
|
||||
- 每帧复制缓存前检查系统可用物理内存;Windows 使用 `GlobalMemoryStatusEx`,Linux 使用 `/proc/meminfo` 的 `MemAvailable`。
|
||||
- 预留 1 GiB,并额外考虑下一帧、容器扩容以及 32 MiB 运行余量。继续缓存可能越过保留线时,本次扫描永久停止追加缓存,不因内存稍后恢复而重新追加。
|
||||
- 内存查询失败也保守停止缓存。内存分配失败同样标记缓存不完整,并安全释放本帧尚未交给缓存的数据。
|
||||
- 已缓存的前缀数据保留;后续逐帧增量算法处理和自动完整 TXT 写入不受缓存停止影响。
|
||||
- 扫描/检测完成后仍保留缓存。下一次扫描或加载新数据时,清空旧缓存并重置不完整标记。
|
||||
- 右键保存时,缓存不完整会提示“内存不足,导致数据不完整”,确认后才能保存部分数据;若一帧都未缓存,直接提示该原因。
|
||||
- 扫描和检测期间不允许手动导出缓存,避免阻塞采集及将扫描中的前缀误认为完整数据。
|
||||
- 其他项目的算法缓存策略不变,仅自动点云保存切换为采集期间写入。
|
||||
|
||||
## 边界与现场确认
|
||||
|
||||
- 1 GiB 是本程序停止增加缓存的保护线,不能保证其他进程或算法的额外分配不会让系统可用内存继续下降。
|
||||
- 有限队列满时产生背压,是否影响设备帧率取决于 SDK 和硬盘吞吐;需使用实际设备、实际单帧点数确认 500 fps、连续三分钟的吞吐,不能仅凭写入逻辑承诺硬件端不丢帧。
|
||||
- 正常扫描检查最终 TXT 可复检、扫描线数与接收帧数一致、帧号覆盖整次扫描。
|
||||
- 低内存扫描检查缓存停止后不再增长,检测正常完成,手动保存明确提示不完整,自动 TXT 仍覆盖后续帧。
|
||||
- 连续两轮扫描检查文件不覆盖、不串帧,不完整标记在新一轮清零。
|
||||
- 检查普通项目单相机/多相机扫描、主动停止、取消、断线、无写权限和磁盘写满路径;失败时不能生成看似成功的完整 TXT。
|
||||
@ -38,7 +38,6 @@ public:
|
||||
QString StopDynamicScanAndDetect();
|
||||
void CancelDynamicScan();
|
||||
ScanState GetScanState() const { return m_scanState.load(); }
|
||||
bool IsDetectionDataCacheIncomplete() const override { return m_cacheIncomplete.load(); }
|
||||
|
||||
int LoadAndDetect(const QString& fileName);
|
||||
void ReconnectCamera();
|
||||
@ -59,13 +58,11 @@ public:
|
||||
|
||||
protected:
|
||||
int InitAlgoParams() override;
|
||||
int ProcessAlgoDetection(std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& detectionDataCache) override;
|
||||
bool OnDetectionData(EVzResultDataType dataType, SVzLaserLineData* laserData) override;
|
||||
int ProcessAlgoDetection(
|
||||
std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& detectionDataCache) override;
|
||||
bool OnDetectionData(EVzResultDataType dataType,
|
||||
SVzLaserLineData* laserData) override;
|
||||
bool HasPendingDetectionData() const override;
|
||||
bool UsesIncrementalDetection() const override { return true; }
|
||||
void OnDetectionDataCacheAllocationFailed() override;
|
||||
bool ShouldCacheDetectionData(EVzResultDataType dataType,
|
||||
const SVzLaserLineData& laserData) override;
|
||||
void ClearDetectionDataCache() override;
|
||||
EVzResultDataType GetDetectionDataType() override { return keResultDataType_Position; }
|
||||
VzNL_OnNotifyStatusCBEx GetCameraStatusCallback() override;
|
||||
@ -117,7 +114,6 @@ private:
|
||||
WeldSeamTCPProtocol* m_pTCPServer = nullptr;
|
||||
DetectPresenter* m_pDetectPresenter = nullptr;
|
||||
std::atomic<ScanState> m_scanState{ScanState::Ready};
|
||||
std::atomic<bool> m_cacheIncomplete{false};
|
||||
std::atomic<bool> m_deinitialized{false};
|
||||
bool m_bTCPConnected = false;
|
||||
bool m_modbusRegistersInitialized = false;
|
||||
|
||||
@ -12,34 +12,6 @@
|
||||
#include <QFile>
|
||||
#include <QMetaObject>
|
||||
#include <cstring>
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
quint64 AvailablePhysicalMemory()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
MEMORYSTATUSEX status{};
|
||||
status.dwLength = sizeof(status);
|
||||
return GlobalMemoryStatusEx(&status) ? status.ullAvailPhys : 0;
|
||||
#elif defined(Q_OS_LINUX)
|
||||
QFile memoryInfo(QStringLiteral("/proc/meminfo"));
|
||||
if (memoryInfo.open(QIODevice::ReadOnly)) {
|
||||
for (const QByteArray& line : memoryInfo.readAll().split('\n')) {
|
||||
if (line.startsWith("MemAvailable:")) {
|
||||
return line.mid(sizeof("MemAvailable:") - 1).simplified().split(' ').value(0).toULongLong() * 1024;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
WeldSeamPresenter::WeldSeamPresenter(QObject* parent)
|
||||
: BasePresenter(parent)
|
||||
@ -137,7 +109,6 @@ void WeldSeamPresenter::DynamicCameraStatusCallback(
|
||||
auto* presenter = static_cast<WeldSeamPresenter*>(context->presenter);
|
||||
switch (status) {
|
||||
case keDeviceWorkStatus_Offline:
|
||||
presenter->FinishDebugPointCloudStream(context->cameraIndex, false);
|
||||
presenter->OnCameraStatusChanged(context->cameraIndex, false);
|
||||
break;
|
||||
case keDeviceWorkStatus_Eye_Reconnect:
|
||||
@ -207,8 +178,6 @@ QString WeldSeamPresenter::StopDynamicScanAndDetect()
|
||||
return QStringLiteral("ERROR_INTERNAL_ERROR");
|
||||
}
|
||||
|
||||
FinishDebugPointCloudStream(m_currentCameraIndex);
|
||||
|
||||
// StopDetect 成功后才检查缓存。空扫描仍先返回 STOP_ACK,
|
||||
// 随后把失败状态写入结果缓存,等待 GET_RET 查询。
|
||||
if (!HasPendingDetectionData()) {
|
||||
@ -238,7 +207,6 @@ void WeldSeamPresenter::CancelDynamicScan()
|
||||
m_vrEyeDeviceList[arrayIndex].second) {
|
||||
m_vrEyeDeviceList[arrayIndex].second->StopDetect();
|
||||
}
|
||||
FinishDebugPointCloudStream(m_currentCameraIndex, false);
|
||||
ClearDetectionDataCache();
|
||||
ClearCachedResult(m_currentCameraIndex);
|
||||
UpdateModbusStatus(m_bCameraConnected ? 0 : 4);
|
||||
@ -276,6 +244,13 @@ int WeldSeamPresenter::ProcessAlgoDetection(
|
||||
int code = ERR_CODE(DEV_NOT_FIND);
|
||||
if (m_pDetectPresenter && m_pConfigManager) {
|
||||
const ConfigResult config = m_pConfigManager->GetConfigResult();
|
||||
// Live WeldSeam scans are preprocessed line-by-line. The base cache is
|
||||
// only populated in debug mode for persistence, so release that copy
|
||||
// before asking DetectPresenter to consume its incremental data.
|
||||
if (m_pDetectPresenter->HasIncrementalData() && !detectionDataCache.empty()) {
|
||||
m_dataLoader.FreeLaserScanData(detectionDataCache);
|
||||
detectionDataCache.clear();
|
||||
}
|
||||
code = m_pDetectPresenter->DetectWeldSeam(
|
||||
m_currentCameraIndex, detectionDataCache, config.algorithmParams,
|
||||
GetCameraCalibParam(m_currentCameraIndex), config.debugParam,
|
||||
@ -324,44 +299,13 @@ bool WeldSeamPresenter::OnDetectionData(EVzResultDataType dataType,
|
||||
GetCameraCalibParam(m_currentCameraIndex), m_dataLoader);
|
||||
if (!processed) {
|
||||
LOG_WARNING("WeldSeam failed to preprocess one point-cloud line\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WeldSeamPresenter::ShouldCacheDetectionData(EVzResultDataType dataType,
|
||||
const SVzLaserLineData& laserData)
|
||||
{
|
||||
if (m_cacheIncomplete.load()) return false;
|
||||
|
||||
const quint64 reserveBytes = 1024ULL * 1024 * 1024;
|
||||
const quint64 lineBytes = DebugDataSaver::GetLineStorageSize(dataType, laserData);
|
||||
quint64 growthBytes = 0;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_detectionDataMutex);
|
||||
if (m_detectionDataCache.size() == m_detectionDataCache.capacity()) {
|
||||
growthBytes = 2 * (m_detectionDataCache.capacity() + 1) * sizeof(m_detectionDataCache.front());
|
||||
}
|
||||
}
|
||||
const quint64 availableBytes = AvailablePhysicalMemory();
|
||||
if (lineBytes == 0 || availableBytes <= reserveBytes + lineBytes + growthBytes + 32ULL * 1024 * 1024) {
|
||||
if (!m_cacheIncomplete.exchange(true)) {
|
||||
LOG_WARNING("WeldSeam cache stopped: available=%llu bytes\n", availableBytes);
|
||||
OnStatusUpdate("内存不足,导致数据不完整");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// 单线处理完成后立即释放 SDK 数据,长距离扫描不再深拷贝完整点云。
|
||||
return true;
|
||||
}
|
||||
|
||||
void WeldSeamPresenter::OnDetectionDataCacheAllocationFailed()
|
||||
{
|
||||
if (!m_cacheIncomplete.exchange(true)) {
|
||||
LOG_WARNING("WeldSeam cache stopped after allocation failure\n");
|
||||
OnStatusUpdate("内存不足,导致数据不完整");
|
||||
}
|
||||
}
|
||||
|
||||
bool WeldSeamPresenter::HasPendingDetectionData() const
|
||||
{
|
||||
return m_pDetectPresenter && m_pDetectPresenter->HasIncrementalData();
|
||||
@ -370,7 +314,6 @@ bool WeldSeamPresenter::HasPendingDetectionData() const
|
||||
void WeldSeamPresenter::ClearDetectionDataCache()
|
||||
{
|
||||
BasePresenter::ClearDetectionDataCache();
|
||||
m_cacheIncomplete.store(false);
|
||||
if (m_pDetectPresenter) m_pDetectPresenter->ResetIncrementalData();
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
#define WELDSEAM_APP_NAME "焊缝定位"
|
||||
#define WELDSEAM_VERSION_STRING "1.1.0"
|
||||
#define WELDSEAM_BUILD_STRING "3"
|
||||
#define WELDSEAM_BUILD_STRING "2"
|
||||
#define WELDSEAM_FULL_VERSION_STRING "V" WELDSEAM_VERSION_STRING "_" WELDSEAM_BUILD_STRING
|
||||
|
||||
// 获取版本信息的便捷函数
|
||||
|
||||
@ -728,11 +728,6 @@ void MainWindow::showContextMenu(const QPoint& pos, QGraphicsView* view)
|
||||
}
|
||||
|
||||
if (m_presenter->GetDetectionDataCacheSize() == 0) {
|
||||
if (m_presenter->IsDetectionDataCacheIncomplete()) {
|
||||
updateStatusLog(tr("内存不足,导致数据不完整"));
|
||||
QMessageBox::warning(this, tr("检测数据不完整"), tr("内存不足,导致数据不完整"));
|
||||
return;
|
||||
}
|
||||
updateStatusLog(tr("没有检测数据可以保存"));
|
||||
return;
|
||||
}
|
||||
@ -749,20 +744,6 @@ void MainWindow::onSaveDetectionData()
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_presenter->GetScanState() != WeldSeamPresenter::ScanState::Ready) {
|
||||
updateStatusLog(tr("请等待扫描和检测结束后再保存检测数据"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_presenter->IsDetectionDataCacheIncomplete()) {
|
||||
updateStatusLog(tr("内存不足,导致数据不完整"));
|
||||
if (QMessageBox::warning(this, tr("检测数据不完整"),
|
||||
tr("内存不足,导致数据不完整\n是否仍然保存已缓存的部分数据?"),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 让用户选择保存目录
|
||||
QString dirPath = QFileDialog::getExistingDirectory(this,
|
||||
tr("选择保存目录"),
|
||||
@ -781,9 +762,7 @@ void MainWindow::onSaveDetectionData()
|
||||
|
||||
// 保存数据
|
||||
if (saveDetectionDataToFile(fullPath, m_presenter->GetDetectIndex())) {
|
||||
updateStatusLog(m_presenter->IsDetectionDataCacheIncomplete()
|
||||
? tr("内存不足,导致数据不完整,部分数据已保存到:%1").arg(fileName.c_str())
|
||||
: tr("检测数据已保存到:%1").arg(fileName.c_str()));
|
||||
updateStatusLog(tr("检测数据已保存到:%1").arg(fileName.c_str()));
|
||||
} else {
|
||||
updateStatusLog(tr("保存检测数据失败"));
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
outRotX="0" outRotY="0" outRotZ="14.56"/>
|
||||
</HandEyeCalibMatrixs>
|
||||
|
||||
<DebugParam enableDebug="true" savePointCloud="true" saveDebugImage="true" printDetailLog="true" crash="false" debugOutputPath="d:/tmp/"/>
|
||||
<DebugParam debugEnable="1" saveImage="0" saveCloud="0" saveResult="0"/>
|
||||
|
||||
<SerialConfig portName="COM6" baudRate="9600" dataBits="8" parity="0" stopBits="1"/>
|
||||
|
||||
|
||||
@ -122,8 +122,6 @@ public:
|
||||
*/
|
||||
int GetDetectionDataCacheSize() const;
|
||||
|
||||
virtual bool IsDetectionDataCacheIncomplete() const { return false; }
|
||||
|
||||
/**
|
||||
* @brief 保存当前缓存的检测数据到文件(便捷方法)
|
||||
*
|
||||
@ -336,19 +334,6 @@ protected:
|
||||
return GetDetectionDataCacheSize() > 0;
|
||||
}
|
||||
|
||||
virtual bool UsesIncrementalDetection() const { return false; }
|
||||
|
||||
virtual void OnDetectionDataCacheAllocationFailed() {
|
||||
OnStatusUpdate("内存不足,检测数据缓存失败");
|
||||
}
|
||||
|
||||
virtual bool ShouldCacheDetectionData(EVzResultDataType dataType,
|
||||
const SVzLaserLineData& laserData) {
|
||||
Q_UNUSED(dataType);
|
||||
Q_UNUSED(laserData);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取检测数据类型(纯虚函数)
|
||||
*
|
||||
@ -506,9 +491,6 @@ protected:
|
||||
*/
|
||||
virtual std::string GetSaveDeviceTag() const { return ""; }
|
||||
|
||||
void FinishDebugPointCloudStream(int cameraIndex, bool complete = true);
|
||||
bool HasStreamingPointCloudSave(int cameraIndex) const;
|
||||
|
||||
// ============ 公共实现方法 ============
|
||||
|
||||
/**
|
||||
@ -594,8 +576,6 @@ private:
|
||||
*/
|
||||
int DetectTask();
|
||||
|
||||
bool StartDebugPointCloudStream(int cameraIndex);
|
||||
|
||||
// 同步覆盖保存现场点云,保存成功后才能进入算法。
|
||||
int SaveCrashDataBeforeProcessing(const VrDebugParam& debugParam, const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& detectionData);
|
||||
|
||||
@ -736,10 +716,6 @@ protected:
|
||||
|
||||
// 调试数据异步存储
|
||||
DebugDataSaver m_debugDataSaver;
|
||||
mutable std::mutex m_debugStreamMutex;
|
||||
std::set<int> m_streamedCameras;
|
||||
std::set<int> m_failedStreamCameras;
|
||||
std::map<int, DebugDataSaveContext> m_streamSaveContexts;
|
||||
|
||||
// ============ 多相机同时扫描相关成员 ============
|
||||
|
||||
|
||||
@ -5,8 +5,6 @@
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
@ -30,13 +28,6 @@ public:
|
||||
|
||||
void Stop();
|
||||
|
||||
bool StartStream(int cameraIndex, const std::string& basePath,
|
||||
const std::string& timestamp, const std::string& deviceName);
|
||||
bool AppendStream(int cameraIndex, EVzResultDataType dataType, const SVzLaserLineData& line);
|
||||
bool FinishStream(int cameraIndex, bool complete = true);
|
||||
void FinishAllStreams();
|
||||
static uint64_t GetLineStorageSize(EVzResultDataType dataType, const SVzLaserLineData& line);
|
||||
|
||||
void SetMaxStorageSize(int64_t bytes) { m_maxStorageSize = bytes; }
|
||||
void SetMaxQueueSize(int maxTasks);
|
||||
void PrepareStorageCache(const std::string& basePath);
|
||||
@ -45,33 +36,6 @@ public:
|
||||
static std::string GenerateSavePath(const std::string& basePath, const std::string& timestamp, const std::string& deviceName = "", bool binary = false);
|
||||
|
||||
private:
|
||||
struct StreamLine {
|
||||
EVzResultDataType dataType;
|
||||
SVzLaserLineData line{};
|
||||
std::unique_ptr<unsigned char[]> points3D;
|
||||
std::unique_ptr<unsigned char[]> points2D;
|
||||
size_t bytes = 0;
|
||||
};
|
||||
|
||||
struct StreamTask {
|
||||
std::string basePath;
|
||||
std::string filePath;
|
||||
std::ofstream output;
|
||||
std::mutex appendMutex;
|
||||
std::mutex queueMutex;
|
||||
std::condition_variable condition;
|
||||
std::queue<std::unique_ptr<StreamLine>> lines;
|
||||
size_t queuedBytes = 0;
|
||||
uint64_t writtenLines = 0;
|
||||
bool closing = false;
|
||||
bool failed = false;
|
||||
std::thread worker;
|
||||
};
|
||||
|
||||
static void StreamWorker(const std::shared_ptr<StreamTask>& task);
|
||||
std::mutex m_streamMutex;
|
||||
std::map<int, std::shared_ptr<StreamTask>> m_streams;
|
||||
|
||||
struct SaveTask {
|
||||
std::string basePath;
|
||||
std::string filePath;
|
||||
@ -99,11 +63,10 @@ private:
|
||||
static void FreeData(std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& data);
|
||||
|
||||
void EnsureStorageLimit(const std::string& basePath);
|
||||
void EnsureStorageLimitLocked(const std::string& basePath, StorageCache& cache,
|
||||
const std::string& protectedPath = "");
|
||||
void EnsureStorageLimitLocked(const std::string& basePath, StorageCache& cache);
|
||||
void BuildStorageCacheLocked(const std::string& basePath, StorageCache& cache);
|
||||
void AddFileToCacheLocked(StorageCache& cache, const StorageFileInfo& fileInfo);
|
||||
void RecordSavedFile(const std::string& basePath, const std::string& filePath, bool protectFile = false);
|
||||
void RecordSavedFile(const std::string& basePath, const std::string& filePath);
|
||||
|
||||
static bool RemoveFileFromCache(StorageCache& cache, const std::string& filePath);
|
||||
static bool IsOlderFile(const StorageFileInfo& lhs, const StorageFileInfo& rhs);
|
||||
|
||||
@ -2,8 +2,6 @@
|
||||
#include "VrLog.h"
|
||||
#include "VrError.h"
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
@ -18,27 +16,6 @@
|
||||
|
||||
namespace {
|
||||
|
||||
template<typename Point3D, typename Point2D, typename CacheFunction>
|
||||
void CopyLineToCache(const SVzLaserLineData& source, CacheFunction&& cache)
|
||||
{
|
||||
SVzLaserLineData copy = source;
|
||||
std::unique_ptr<Point3D[]> points3D;
|
||||
std::unique_ptr<Point2D[]> points2D;
|
||||
if (source.nPointCount > 0) {
|
||||
points3D = std::make_unique<Point3D[]>(source.nPointCount);
|
||||
points2D = std::make_unique<Point2D[]>(source.nPointCount);
|
||||
std::memcpy(points3D.get(), source.p3DPoint, sizeof(Point3D) * source.nPointCount);
|
||||
if (source.p2DPoint) {
|
||||
std::memcpy(points2D.get(), source.p2DPoint, sizeof(Point2D) * source.nPointCount);
|
||||
}
|
||||
}
|
||||
copy.p3DPoint = points3D.get();
|
||||
copy.p2DPoint = points2D.get();
|
||||
cache(copy);
|
||||
points3D.release();
|
||||
points2D.release();
|
||||
}
|
||||
|
||||
QString ResolveDebugOutputPath(const VrDebugParam& debugParam)
|
||||
{
|
||||
const QString configuredPath = debugParam.debugOutputPath.empty()
|
||||
@ -194,7 +171,8 @@ BasePresenter::~BasePresenter()
|
||||
// 处理待处理的 Qt 事件,确保 QueuedConnection 的回调执行时检查到 m_pStatusCallback 为空
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
StopDetection();
|
||||
// 停止检测线程
|
||||
StopAlgoDetectThread();
|
||||
|
||||
// 停止调试数据异步存储
|
||||
m_debugDataSaver.Stop();
|
||||
@ -303,9 +281,7 @@ int BasePresenter::StartDetection(int cameraIndex, bool isAuto)
|
||||
CameraCallbackContext* ctx = m_cameraContexts[currentCamera];
|
||||
pDevice->SetStatusCallback(statusCallback, ctx);
|
||||
|
||||
if (!StartDebugPointCloudStream(currentCamera)) return ERR_CODE(FILE_ERR_WRITE);
|
||||
int nRet = pDevice->StartDetect(detectCallback, eDataType, ctx);
|
||||
if (nRet != SUCCESS) FinishDebugPointCloudStream(currentCamera, false);
|
||||
LOG_INFO("[BasePresenter] Camera %d start detection result: %d\n", currentCamera, nRet);
|
||||
|
||||
if (nRet == SUCCESS) {
|
||||
@ -346,9 +322,7 @@ int BasePresenter::StartDetection(int cameraIndex, bool isAuto)
|
||||
CameraCallbackContext* ctx = m_cameraContexts[currentCamera];
|
||||
pDevice->SetStatusCallback(statusCallback, ctx);
|
||||
|
||||
if (!StartDebugPointCloudStream(currentCamera)) return ERR_CODE(FILE_ERR_WRITE);
|
||||
int nRet = pDevice->StartDetect(detectCallback, eDataType, ctx);
|
||||
if (nRet != SUCCESS) FinishDebugPointCloudStream(currentCamera, false);
|
||||
LOG_INFO("[BasePresenter] Camera %d start detection result: %d\n", currentCamera, nRet);
|
||||
|
||||
if (nRet == SUCCESS) {
|
||||
@ -418,7 +392,6 @@ int BasePresenter::StopDetection()
|
||||
if (pDevice) {
|
||||
int ret = pDevice->StopDetect();
|
||||
if (ret == 0) {
|
||||
FinishDebugPointCloudStream(static_cast<int>(i + 1));
|
||||
LOG_INFO("[BasePresenter] Camera %zu stop detection successfully\n", i + 1);
|
||||
} else {
|
||||
LOG_WARNING("[BasePresenter] Camera %zu stop detection failed, error code: %d\n", i + 1, ret);
|
||||
@ -426,8 +399,6 @@ int BasePresenter::StopDetection()
|
||||
}
|
||||
}
|
||||
|
||||
m_debugDataSaver.FinishAllStreams();
|
||||
|
||||
// 停止算法检测线程
|
||||
StopAlgoDetectThread();
|
||||
|
||||
@ -477,13 +448,6 @@ int BasePresenter::SaveDetectionDataToFile(const std::string& filePath)
|
||||
|
||||
int BasePresenter::LoadDebugDataAndDetect(const std::string& filePath)
|
||||
{
|
||||
m_debugDataSaver.FinishAllStreams();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_debugStreamMutex);
|
||||
m_streamedCameras.clear();
|
||||
m_failedStreamCameras.clear();
|
||||
m_streamSaveContexts.clear();
|
||||
}
|
||||
SetWorkStatus(WorkStatus::Working);
|
||||
LOG_INFO("[BasePresenter] Loading debug data from file: %s\n", filePath.c_str());
|
||||
|
||||
@ -893,71 +857,12 @@ BasePresenter::DebugDataSaveContext BasePresenter::CreateDebugDataSaveContext(co
|
||||
|
||||
BasePresenter::DebugDataSaveContext BasePresenter::GetDebugDataSaveContext(const VrDebugParam& debugParam) const
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_debugStreamMutex);
|
||||
const auto entry = m_streamSaveContexts.find(m_currentCameraIndex);
|
||||
if (entry != m_streamSaveContexts.end()) return entry->second;
|
||||
}
|
||||
return CreateDebugDataSaveContext(debugParam, m_currentCameraIndex);
|
||||
}
|
||||
|
||||
bool BasePresenter::HasStreamingPointCloudSave(int cameraIndex) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_debugStreamMutex);
|
||||
return m_streamedCameras.count(cameraIndex) != 0;
|
||||
}
|
||||
|
||||
bool BasePresenter::StartDebugPointCloudStream(int cameraIndex)
|
||||
{
|
||||
m_debugDataSaver.FinishStream(cameraIndex, false);
|
||||
const VrDebugParam debugParam = GetDebugParam();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_debugStreamMutex);
|
||||
m_streamedCameras.erase(cameraIndex);
|
||||
m_failedStreamCameras.erase(cameraIndex);
|
||||
m_streamSaveContexts.erase(cameraIndex);
|
||||
if (!debugParam.enableDebug || !debugParam.savePointCloud) return true;
|
||||
m_streamedCameras.insert(cameraIndex);
|
||||
}
|
||||
const DebugDataSaveContext context = cameraIndex == m_currentCameraIndex
|
||||
? GetDebugDataSaveContext(debugParam) : CreateDebugDataSaveContext(debugParam, cameraIndex);
|
||||
bool started = false;
|
||||
try {
|
||||
started = m_debugDataSaver.StartStream(cameraIndex, context.basePath, context.timestamp, context.deviceName);
|
||||
} catch (const std::exception& error) {
|
||||
LOG_ERROR("[BasePresenter] cannot start point-cloud stream: %s\n", error.what());
|
||||
}
|
||||
if (!started) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_debugStreamMutex);
|
||||
m_failedStreamCameras.insert(cameraIndex);
|
||||
}
|
||||
OnStatusUpdate("点云实时保存文件创建失败,未启动扫描");
|
||||
SetWorkStatus(WorkStatus::Error);
|
||||
return false;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_debugStreamMutex);
|
||||
m_streamSaveContexts[cameraIndex] = context;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void BasePresenter::FinishDebugPointCloudStream(int cameraIndex, bool complete)
|
||||
{
|
||||
if (!m_debugDataSaver.FinishStream(cameraIndex, complete) && complete) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_debugStreamMutex);
|
||||
m_failedStreamCameras.insert(cameraIndex);
|
||||
}
|
||||
OnStatusUpdate("点云实时保存失败,文件数据不完整(保留 .part 文件)");
|
||||
}
|
||||
}
|
||||
|
||||
int BasePresenter::DetectTask()
|
||||
{
|
||||
LOG_INFO("[BasePresenter] DetectTask - 开始执行检测任务\n");
|
||||
FinishDebugPointCloudStream(m_currentCameraIndex);
|
||||
|
||||
// 获取调试参数
|
||||
VrDebugParam debugParam = GetDebugParam();
|
||||
@ -997,15 +902,10 @@ int BasePresenter::DetectTask()
|
||||
|
||||
// 2. crash 模式同步覆盖保存原始输入,独立于普通点云保存开关和预处理保存策略。
|
||||
int nRet = SUCCESS;
|
||||
if (debugParam.enableDebug && debugParam.crash &&
|
||||
!(UsesIncrementalDetection() && IsDetectionDataCacheIncomplete()))
|
||||
if (debugParam.enableDebug && debugParam.crash)
|
||||
{
|
||||
nRet = SaveCrashDataBeforeProcessing(debugParam, localData);
|
||||
}
|
||||
else if (HasStreamingPointCloudSave(m_currentCameraIndex))
|
||||
{
|
||||
LOG_INFO("[BasePresenter] point cloud already submitted during acquisition\n");
|
||||
}
|
||||
else if (!SavePointCloudBeforeProcessing())
|
||||
{
|
||||
LOG_INFO("[BasePresenter] point-cloud debug save delegated to application preprocessing\n");
|
||||
@ -1150,54 +1050,100 @@ void BasePresenter::_StaticDetectionCallback(EVzResultDataType eDataType, SVzLas
|
||||
BasePresenter* pThis = ctx->presenter;
|
||||
int cameraIndex = ctx->cameraIndex;
|
||||
|
||||
if (!pThis->m_debugDataSaver.AppendStream(cameraIndex, eDataType, *pLaserLinePoint)) {
|
||||
bool firstFailure = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(pThis->m_debugStreamMutex);
|
||||
firstFailure = pThis->m_failedStreamCameras.insert(cameraIndex).second;
|
||||
}
|
||||
if (firstFailure) pThis->OnStatusUpdate("点云实时保存失败,文件数据不完整(保留 .part 文件)");
|
||||
}
|
||||
|
||||
// crash 模式先缓存完整原始输入,保存完成后再由算法入口处理,避免预处理提前消费现场。
|
||||
const VrDebugParam debugParam = pThis->GetDebugParam();
|
||||
if (!(debugParam.enableDebug && debugParam.crash && !pThis->UsesIncrementalDetection()) &&
|
||||
if (!(debugParam.enableDebug && debugParam.crash) &&
|
||||
pThis->OnDetectionData(eDataType, pLaserLinePoint))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pThis->ShouldCacheDetectionData(eDataType, *pLaserLinePoint)) {
|
||||
return;
|
||||
// 创建 SVzLaserLineData 副本
|
||||
SVzLaserLineData lineData;
|
||||
memset(&lineData, 0, sizeof(SVzLaserLineData));
|
||||
|
||||
// 根据数据类型分配和复制点云数据
|
||||
if (eDataType == keResultDataType_Position) {
|
||||
// 复制 SVzNL3DPosition 数据
|
||||
if (pLaserLinePoint->p3DPoint && pLaserLinePoint->nPointCount > 0) {
|
||||
lineData.p3DPoint = new SVzNL3DPosition[pLaserLinePoint->nPointCount];
|
||||
if (lineData.p3DPoint) {
|
||||
if(pLaserLinePoint->p3DPoint){
|
||||
memcpy(lineData.p3DPoint, pLaserLinePoint->p3DPoint, sizeof(SVzNL3DPosition) * pLaserLinePoint->nPointCount);
|
||||
} else {
|
||||
memset(lineData.p3DPoint, 0, sizeof(SVzNL3DPosition) * pLaserLinePoint->nPointCount);
|
||||
}
|
||||
}
|
||||
lineData.p2DPoint = new SVzNL2DPosition[pLaserLinePoint->nPointCount];
|
||||
if (lineData.p2DPoint){
|
||||
if(pLaserLinePoint->p2DPoint) {
|
||||
memcpy(lineData.p2DPoint, pLaserLinePoint->p2DPoint, sizeof(SVzNL2DPosition) * pLaserLinePoint->nPointCount);
|
||||
} else {
|
||||
memset(lineData.p2DPoint, 0, sizeof(SVzNL2DPosition) * pLaserLinePoint->nPointCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (eDataType == keResultDataType_PositionF) {
|
||||
if (pLaserLinePoint->p3DPoint && pLaserLinePoint->nPointCount > 0) {
|
||||
lineData.p3DPoint = new SVzNL3DPosition[pLaserLinePoint->nPointCount];
|
||||
if (lineData.p3DPoint) {
|
||||
memcpy(lineData.p3DPoint,
|
||||
pLaserLinePoint->p3DPoint,
|
||||
sizeof(SVzNL3DPosition) * pLaserLinePoint->nPointCount);
|
||||
}
|
||||
|
||||
lineData.p2DPoint = new SVzNL2DPositionF[pLaserLinePoint->nPointCount];
|
||||
if (lineData.p2DPoint) {
|
||||
if (pLaserLinePoint->p2DPoint) {
|
||||
memcpy(lineData.p2DPoint,
|
||||
pLaserLinePoint->p2DPoint,
|
||||
sizeof(SVzNL2DPositionF) * pLaserLinePoint->nPointCount);
|
||||
} else {
|
||||
memset(lineData.p2DPoint,
|
||||
0,
|
||||
sizeof(SVzNL2DPositionF) * pLaserLinePoint->nPointCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (eDataType == keResultDataType_PointXYZRGBA) {
|
||||
// 复制 SVzNLPointXYZRGBA 数据
|
||||
if (pLaserLinePoint->p3DPoint && pLaserLinePoint->nPointCount > 0) {
|
||||
lineData.p3DPoint = new SVzNLPointXYZRGBA[pLaserLinePoint->nPointCount];
|
||||
if (lineData.p3DPoint) {
|
||||
if(pLaserLinePoint->p3DPoint){
|
||||
memcpy(lineData.p3DPoint, pLaserLinePoint->p3DPoint, sizeof(SVzNLPointXYZRGBA) * pLaserLinePoint->nPointCount);
|
||||
} else {
|
||||
memset(lineData.p3DPoint, 0, sizeof(SVzNLPointXYZRGBA) * pLaserLinePoint->nPointCount);
|
||||
}
|
||||
}
|
||||
lineData.p2DPoint = new SVzNL2DLRPoint[pLaserLinePoint->nPointCount];
|
||||
if (lineData.p2DPoint) {
|
||||
if(pLaserLinePoint->p2DPoint) {
|
||||
memcpy(lineData.p2DPoint, pLaserLinePoint->p2DPoint, sizeof(SVzNL2DLRPoint) * pLaserLinePoint->nPointCount);
|
||||
} else {
|
||||
memset(lineData.p2DPoint, 0, sizeof(SVzNL2DLRPoint) * pLaserLinePoint->nPointCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto cacheLine = [pThis, cameraIndex, eDataType](const SVzLaserLineData& lineData) {
|
||||
if (pThis->m_batchInProgress.load()) {
|
||||
pThis->AddDetectionDataToCameraCache(cameraIndex, eDataType, lineData);
|
||||
} else {
|
||||
pThis->AddDetectionDataToCache(eDataType, lineData);
|
||||
}
|
||||
};
|
||||
try {
|
||||
switch (eDataType) {
|
||||
case keResultDataType_Position:
|
||||
CopyLineToCache<SVzNL3DPosition, SVzNL2DPosition>(*pLaserLinePoint, cacheLine);
|
||||
break;
|
||||
case keResultDataType_PositionF:
|
||||
CopyLineToCache<SVzNL3DPosition, SVzNL2DPositionF>(*pLaserLinePoint, cacheLine);
|
||||
break;
|
||||
case keResultDataType_PointXYZRGBA:
|
||||
CopyLineToCache<SVzNLPointXYZRGBA, SVzNL2DLRPoint>(*pLaserLinePoint, cacheLine);
|
||||
break;
|
||||
case keResultDataType_PointXYZI:
|
||||
CopyLineToCache<SVzNLPointXYZI, SVzNL2DLRPoint>(*pLaserLinePoint, cacheLine);
|
||||
break;
|
||||
default:
|
||||
LOG_WARNING("[BasePresenter] unsupported cache data type: %d\n", static_cast<int>(eDataType));
|
||||
break;
|
||||
}
|
||||
} catch (const std::bad_alloc&) {
|
||||
pThis->OnDetectionDataCacheAllocationFailed();
|
||||
// 复制其他字段
|
||||
lineData.nPointCount = pLaserLinePoint->nPointCount;
|
||||
lineData.dTotleOffset = pLaserLinePoint->dTotleOffset;
|
||||
lineData.dStep = pLaserLinePoint->dStep;
|
||||
lineData.llTimeStamp = pLaserLinePoint->llTimeStamp;
|
||||
lineData.llFrameIdx = pLaserLinePoint->llFrameIdx;
|
||||
lineData.nEncodeNo = pLaserLinePoint->nEncodeNo;
|
||||
lineData.fSwingAngle = pLaserLinePoint->fSwingAngle;
|
||||
lineData.bEndOnceScan = pLaserLinePoint->bEndOnceScan;
|
||||
|
||||
// 根据扫描模式分流存储
|
||||
if (pThis->m_batchInProgress.load()) {
|
||||
// 多相机分批扫描模式:存入该相机的独立缓存
|
||||
pThis->AddDetectionDataToCameraCache(cameraIndex, eDataType, lineData);
|
||||
} else {
|
||||
// 单相机模式:存入共享缓存
|
||||
pThis->AddDetectionDataToCache(eDataType, lineData);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1221,7 +1167,6 @@ void BasePresenter::_StaticCameraStatusCallback(EVzDeviceWorkStatus eStatus, voi
|
||||
LOG_WARNING("[BasePresenter Camera Status Callback] Camera %d offline/disconnected\n", cameraIndex);
|
||||
|
||||
pThis->m_bCameraConnected = false;
|
||||
pThis->FinishDebugPointCloudStream(cameraIndex, false);
|
||||
pThis->OnCameraStatusChanged(cameraIndex, false);
|
||||
break;
|
||||
}
|
||||
@ -1238,7 +1183,6 @@ void BasePresenter::_StaticCameraStatusCallback(EVzDeviceWorkStatus eStatus, voi
|
||||
case EVzDeviceWorkStatus::keDeviceWorkStatus_Device_Swing_Finish:
|
||||
{
|
||||
LOG_INFO("[BasePresenter Camera Status Callback] Camera %d scan finished\n", cameraIndex);
|
||||
pThis->FinishDebugPointCloudStream(cameraIndex);
|
||||
|
||||
// 分批模式下记录批次完成
|
||||
if (pThis->m_batchInProgress.load()) {
|
||||
@ -1511,14 +1455,7 @@ void BasePresenter::StartBatchScan()
|
||||
CameraCallbackContext* ctx = m_cameraContexts[cameraIndex];
|
||||
pDevice->SetStatusCallback(statusCallback, ctx);
|
||||
|
||||
int nRet = StartDebugPointCloudStream(cameraIndex)
|
||||
? pDevice->StartDetect(detectCallback, eDataType, ctx) : ERR_CODE(FILE_ERR_WRITE);
|
||||
if (nRet != SUCCESS) {
|
||||
FinishDebugPointCloudStream(cameraIndex, false);
|
||||
OnCameraScanFinished(cameraIndex);
|
||||
m_algoDetectTaskPending.store(true);
|
||||
m_algoDetectCondition.notify_one();
|
||||
}
|
||||
int nRet = pDevice->StartDetect(detectCallback, eDataType, ctx);
|
||||
LOG_INFO("[BasePresenter] Camera %d start detect: %d\n", cameraIndex, nRet);
|
||||
}
|
||||
|
||||
|
||||
@ -14,29 +14,9 @@
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace {
|
||||
|
||||
std::pair<size_t, size_t> PointStorageSizes(EVzResultDataType dataType)
|
||||
{
|
||||
switch (dataType) {
|
||||
case keResultDataType_Position:
|
||||
return {sizeof(SVzNL3DPosition), sizeof(SVzNL2DPosition)};
|
||||
case keResultDataType_PositionF:
|
||||
return {sizeof(SVzNL3DPosition), sizeof(SVzNL2DPositionF)};
|
||||
case keResultDataType_PointXYZRGBA:
|
||||
return {sizeof(SVzNLPointXYZRGBA), sizeof(SVzNL2DLRPoint)};
|
||||
case keResultDataType_PointXYZI:
|
||||
return {sizeof(SVzNLPointXYZI), sizeof(SVzNL2DLRPoint)};
|
||||
default:
|
||||
return {0, 0};
|
||||
}
|
||||
}
|
||||
|
||||
QString AppName()
|
||||
{
|
||||
QString name = QFileInfo(QCoreApplication::applicationFilePath()).baseName();
|
||||
@ -111,7 +91,6 @@ DebugDataSaver::~DebugDataSaver()
|
||||
|
||||
void DebugDataSaver::Stop()
|
||||
{
|
||||
FinishAllStreams();
|
||||
if (!m_running.exchange(false)) {
|
||||
return;
|
||||
}
|
||||
@ -154,198 +133,6 @@ void DebugDataSaver::PrepareStorageCache(const std::string& basePath)
|
||||
EnsureStorageLimitLocked(basePath, cache);
|
||||
}
|
||||
|
||||
uint64_t DebugDataSaver::GetLineStorageSize(EVzResultDataType dataType, const SVzLaserLineData& line)
|
||||
{
|
||||
if (line.nPointCount < 0) return 0;
|
||||
const auto sizes = PointStorageSizes(dataType);
|
||||
if (sizes.first == 0) return 0;
|
||||
return sizeof(StreamLine) + static_cast<uint64_t>(sizes.first + sizes.second) * line.nPointCount;
|
||||
}
|
||||
|
||||
bool DebugDataSaver::StartStream(int cameraIndex, const std::string& basePath,
|
||||
const std::string& timestamp, const std::string& deviceName)
|
||||
{
|
||||
FinishStream(cameraIndex, false);
|
||||
if (!m_running.load()) return false;
|
||||
PrepareStorageCache(basePath);
|
||||
auto task = std::make_shared<StreamTask>();
|
||||
task->basePath = basePath;
|
||||
const QString requested = QString::fromStdString(GenerateSavePath(basePath, timestamp, deviceName));
|
||||
const QFileInfo info(requested);
|
||||
if (!info.absoluteDir().mkpath(QStringLiteral("."))) return false;
|
||||
QString path = requested;
|
||||
int suffix = 0;
|
||||
while (QFileInfo::exists(path) || QFileInfo::exists(path + QStringLiteral(".part"))) {
|
||||
path = info.absoluteDir().filePath(info.completeBaseName() + QStringLiteral("_%1.txt").arg(++suffix));
|
||||
}
|
||||
task->filePath = path.toStdString();
|
||||
task->output.open(task->filePath + ".part", std::ios::binary | std::ios::trunc);
|
||||
task->output << "LineNum:" << std::left << std::setw(20) << 0
|
||||
<< "\nDataType:0\nScanSpeed:0\nPointAdjust: 1\nMaxTimeStamp:0_0\n";
|
||||
task->output.flush();
|
||||
if (!task->output.good()) {
|
||||
LOG_ERROR("[DebugDataSaver] stream open failed: %s\n", task->filePath.c_str());
|
||||
return false;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_streamMutex);
|
||||
m_streams[cameraIndex] = task;
|
||||
try {
|
||||
task->worker = std::thread(&DebugDataSaver::StreamWorker, task);
|
||||
} catch (const std::exception& error) {
|
||||
m_streams.erase(cameraIndex);
|
||||
LOG_ERROR("[DebugDataSaver] stream worker failed: %s\n", error.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
LOG_INFO("[DebugDataSaver] streaming TXT started: %s\n", task->filePath.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DebugDataSaver::AppendStream(int cameraIndex, EVzResultDataType dataType, const SVzLaserLineData& line)
|
||||
{
|
||||
std::shared_ptr<StreamTask> task;
|
||||
std::unique_lock<std::mutex> appendLock;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_streamMutex);
|
||||
const auto entry = m_streams.find(cameraIndex);
|
||||
if (entry == m_streams.end()) return true;
|
||||
task = entry->second;
|
||||
appendLock = std::unique_lock<std::mutex>(task->appendMutex);
|
||||
}
|
||||
try {
|
||||
const uint64_t bytes = GetLineStorageSize(dataType, line);
|
||||
const size_t maxQueuedBytes = 8 * 1024 * 1024;
|
||||
if (bytes == 0 || bytes > maxQueuedBytes || (line.nPointCount > 0 && !line.p3DPoint)) {
|
||||
throw std::runtime_error("Invalid point-cloud line");
|
||||
}
|
||||
std::unique_lock<std::mutex> lock(task->queueMutex);
|
||||
task->condition.wait(lock, [&]() {
|
||||
return task->closing || task->failed || task->queuedBytes + bytes <= maxQueuedBytes;
|
||||
});
|
||||
if (task->closing || task->failed) return false;
|
||||
lock.unlock();
|
||||
auto copy = std::make_unique<StreamLine>();
|
||||
copy->dataType = dataType;
|
||||
copy->line = line;
|
||||
copy->line.p3DPoint = nullptr;
|
||||
copy->line.p2DPoint = nullptr;
|
||||
copy->bytes = static_cast<size_t>(bytes);
|
||||
const auto sizes = PointStorageSizes(dataType);
|
||||
if (line.nPointCount > 0) {
|
||||
const size_t bytes3D = sizes.first * line.nPointCount;
|
||||
const size_t bytes2D = sizes.second * line.nPointCount;
|
||||
copy->points3D = std::make_unique<unsigned char[]>(bytes3D);
|
||||
std::memcpy(copy->points3D.get(), line.p3DPoint, bytes3D);
|
||||
copy->points2D = std::make_unique<unsigned char[]>(bytes2D);
|
||||
if (line.p2DPoint) std::memcpy(copy->points2D.get(), line.p2DPoint, bytes2D);
|
||||
copy->line.p3DPoint = copy->points3D.get();
|
||||
copy->line.p2DPoint = copy->points2D.get();
|
||||
}
|
||||
lock.lock();
|
||||
if (task->closing || task->failed) return false;
|
||||
task->lines.push(std::move(copy));
|
||||
task->queuedBytes += bytes;
|
||||
task->condition.notify_all();
|
||||
return true;
|
||||
} catch (const std::exception& error) {
|
||||
std::lock_guard<std::mutex> lock(task->queueMutex);
|
||||
task->failed = true;
|
||||
task->condition.notify_all();
|
||||
LOG_ERROR("[DebugDataSaver] stream enqueue failed: %s\n", error.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void DebugDataSaver::StreamWorker(const std::shared_ptr<StreamTask>& task)
|
||||
{
|
||||
try {
|
||||
auto lastFlush = std::chrono::steady_clock::now();
|
||||
while (true) {
|
||||
std::unique_ptr<StreamLine> line;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(task->queueMutex);
|
||||
task->condition.wait_for(lock, std::chrono::milliseconds(500), [&]() {
|
||||
return task->closing || task->failed || !task->lines.empty();
|
||||
});
|
||||
if (task->lines.empty() && (task->closing || task->failed)) break;
|
||||
if (!task->lines.empty()) {
|
||||
line = std::move(task->lines.front());
|
||||
task->lines.pop();
|
||||
}
|
||||
}
|
||||
if (line && !LaserDataLoader::WriteLaserScanLine(task->output, line->dataType, line->line)) {
|
||||
throw std::runtime_error("Point-cloud TXT write failed");
|
||||
}
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (now - lastFlush >= std::chrono::milliseconds(500)) {
|
||||
task->output.flush();
|
||||
lastFlush = now;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(task->queueMutex);
|
||||
if (line) task->queuedBytes -= line->bytes;
|
||||
if (!task->output.good()) {
|
||||
task->failed = true;
|
||||
task->condition.notify_all();
|
||||
LOG_ERROR("[DebugDataSaver] stream write failed: %s\n", task->filePath.c_str());
|
||||
break;
|
||||
}
|
||||
if (line) ++task->writtenLines;
|
||||
task->condition.notify_all();
|
||||
}
|
||||
} catch (const std::exception& error) {
|
||||
std::lock_guard<std::mutex> lock(task->queueMutex);
|
||||
task->failed = true;
|
||||
task->condition.notify_all();
|
||||
LOG_ERROR("[DebugDataSaver] stream worker failed: %s\n", error.what());
|
||||
}
|
||||
}
|
||||
|
||||
bool DebugDataSaver::FinishStream(int cameraIndex, bool complete)
|
||||
{
|
||||
std::shared_ptr<StreamTask> task;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_streamMutex);
|
||||
const auto entry = m_streams.find(cameraIndex);
|
||||
if (entry == m_streams.end()) return true;
|
||||
task = entry->second;
|
||||
m_streams.erase(entry);
|
||||
}
|
||||
std::lock_guard<std::mutex> appendLock(task->appendMutex);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(task->queueMutex);
|
||||
task->closing = true;
|
||||
task->condition.notify_all();
|
||||
}
|
||||
if (task->worker.joinable()) task->worker.join();
|
||||
task->output.seekp(sizeof("LineNum:") - 1);
|
||||
task->output << std::left << std::setw(20) << task->writtenLines;
|
||||
task->output.flush();
|
||||
const bool written = task->output.good() && !task->failed &&
|
||||
task->writtenLines <= static_cast<uint64_t>((std::numeric_limits<int>::max)());
|
||||
task->output.close();
|
||||
bool success = written && task->output.good() && complete;
|
||||
std::string savedPath = task->filePath + ".part";
|
||||
if (success) {
|
||||
success = QFile::rename(QString::fromStdString(savedPath), QString::fromStdString(task->filePath));
|
||||
if (success) savedPath = task->filePath;
|
||||
}
|
||||
LOG_INFO("[DebugDataSaver] stream finished: complete=%d lines=%llu path=%s\n",
|
||||
success ? 1 : 0, static_cast<unsigned long long>(task->writtenLines), savedPath.c_str());
|
||||
RecordSavedFile(task->basePath, savedPath, true);
|
||||
return success;
|
||||
}
|
||||
|
||||
void DebugDataSaver::FinishAllStreams()
|
||||
{
|
||||
std::vector<int> cameras;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_streamMutex);
|
||||
for (const auto& entry : m_streams) cameras.push_back(entry.first);
|
||||
}
|
||||
for (int cameraIndex : cameras) FinishStream(cameraIndex, false);
|
||||
}
|
||||
|
||||
void DebugDataSaver::SaveAsync(const std::string& basePath,
|
||||
const std::string& timestamp,
|
||||
const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& data,
|
||||
@ -538,8 +325,7 @@ void DebugDataSaver::EnsureStorageLimit(const std::string& basePath)
|
||||
EnsureStorageLimitLocked(basePath, cache);
|
||||
}
|
||||
|
||||
void DebugDataSaver::EnsureStorageLimitLocked(const std::string& basePath, StorageCache& cache,
|
||||
const std::string& protectedPath)
|
||||
void DebugDataSaver::EnsureStorageLimitLocked(const std::string& basePath, StorageCache& cache)
|
||||
{
|
||||
Q_UNUSED(basePath);
|
||||
|
||||
@ -549,12 +335,8 @@ void DebugDataSaver::EnsureStorageLimitLocked(const std::string& basePath, Stora
|
||||
}
|
||||
|
||||
while (cache.totalSize > limit && !cache.files.empty()) {
|
||||
auto candidate = std::find_if(cache.files.begin(), cache.files.end(), [&](const StorageFileInfo& file) {
|
||||
return file.path != protectedPath;
|
||||
});
|
||||
if (candidate == cache.files.end()) break;
|
||||
StorageFileInfo oldest = *candidate;
|
||||
cache.files.erase(candidate);
|
||||
StorageFileInfo oldest = cache.files.front();
|
||||
cache.files.erase(cache.files.begin());
|
||||
|
||||
QString filePath = QString::fromStdString(oldest.path);
|
||||
QFileInfo info(filePath);
|
||||
@ -625,7 +407,7 @@ void DebugDataSaver::AddFileToCacheLocked(StorageCache& cache, const StorageFile
|
||||
cache.totalSize += fileInfo.size;
|
||||
}
|
||||
|
||||
void DebugDataSaver::RecordSavedFile(const std::string& basePath, const std::string& filePath, bool protectFile)
|
||||
void DebugDataSaver::RecordSavedFile(const std::string& basePath, const std::string& filePath)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_diskSpaceMutex);
|
||||
StorageCache& cache = m_storageCaches[NormalizeBasePath(basePath)];
|
||||
@ -643,7 +425,7 @@ void DebugDataSaver::RecordSavedFile(const std::string& basePath, const std::str
|
||||
AddFileToCacheLocked(cache, fileInfo);
|
||||
}
|
||||
|
||||
EnsureStorageLimitLocked(basePath, cache, protectFile ? normalizedFilePath : std::string());
|
||||
EnsureStorageLimitLocked(basePath, cache);
|
||||
}
|
||||
|
||||
bool DebugDataSaver::RemoveFileFromCache(StorageCache& cache, const std::string& filePath)
|
||||
|
||||
@ -386,7 +386,7 @@ void MainWindow::readConfig()
|
||||
}
|
||||
QJsonObject request;
|
||||
request[QStringLiteral("msgType")] = QStringLiteral("GET_CONFIG");
|
||||
request[QStringLiteral("protocolVersion")] = QStringLiteral("2.1");
|
||||
request[QStringLiteral("protocolVersion")] = QStringLiteral("1.6");
|
||||
request[QStringLiteral("requestId")] = QStringLiteral("cfg-%1")
|
||||
.arg(QDateTime::currentMSecsSinceEpoch());
|
||||
sendJsonMessage(request);
|
||||
@ -430,7 +430,7 @@ void MainWindow::setConfig()
|
||||
config[QStringLiteral("queueProcessParam")] = queue;
|
||||
config[QStringLiteral("pointCloudSendParam")] = pointCloud;
|
||||
request[QStringLiteral("msgType")] = QStringLiteral("SET_CONFIG");
|
||||
request[QStringLiteral("protocolVersion")] = QStringLiteral("2.1");
|
||||
request[QStringLiteral("protocolVersion")] = QStringLiteral("1.6");
|
||||
request[QStringLiteral("requestId")] = QStringLiteral("cfg-%1")
|
||||
.arg(QDateTime::currentMSecsSinceEpoch());
|
||||
request[QStringLiteral("config")] = config;
|
||||
@ -447,7 +447,7 @@ void MainWindow::setRealtime()
|
||||
|
||||
QJsonObject request;
|
||||
request[QStringLiteral("msgType")] = QStringLiteral("SET_REALTIME");
|
||||
request[QStringLiteral("protocolVersion")] = QStringLiteral("2.1");
|
||||
request[QStringLiteral("protocolVersion")] = QStringLiteral("1.6");
|
||||
request[QStringLiteral("timestamp")] = QDateTime::currentMSecsSinceEpoch();
|
||||
request[QStringLiteral("enable")] = m_realtimeCheck->isChecked();
|
||||
sendJsonMessage(request);
|
||||
@ -464,7 +464,7 @@ void MainWindow::resetAlarm()
|
||||
|
||||
QJsonObject request;
|
||||
request[QStringLiteral("msgType")] = QStringLiteral("RESET_ALARM");
|
||||
request[QStringLiteral("protocolVersion")] = QStringLiteral("2.1");
|
||||
request[QStringLiteral("protocolVersion")] = QStringLiteral("1.6");
|
||||
request[QStringLiteral("timestamp")] = QDateTime::currentMSecsSinceEpoch();
|
||||
sendJsonMessage(request);
|
||||
logMessage(QStringLiteral("已发送 RESET_ALARM"));
|
||||
@ -479,7 +479,7 @@ void MainWindow::restDetect()
|
||||
|
||||
QJsonObject request;
|
||||
request[QStringLiteral("msgType")] = QStringLiteral("REST_DETECT");
|
||||
request[QStringLiteral("protocolVersion")] = QStringLiteral("2.1");
|
||||
request[QStringLiteral("protocolVersion")] = QStringLiteral("1.6");
|
||||
request[QStringLiteral("timestamp")] = QDateTime::currentMSecsSinceEpoch();
|
||||
sendJsonMessage(request);
|
||||
logMessage(QStringLiteral("已发送 REST_DETECT"));
|
||||
@ -574,7 +574,7 @@ void MainWindow::requestProtocolInfo()
|
||||
|
||||
QJsonObject request;
|
||||
request[QStringLiteral("msgType")] = QStringLiteral("GET_PROTOCOL_INFO");
|
||||
request[QStringLiteral("protocolVersion")] = QStringLiteral("2.1");
|
||||
request[QStringLiteral("protocolVersion")] = QStringLiteral("1.6");
|
||||
sendJsonMessage(request);
|
||||
logMessage(QStringLiteral("已发送新协议 GET_PROTOCOL_INFO 请求"));
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user