#ifndef DEBUG_DATA_SAVER_H #define DEBUG_DATA_SAVER_H #include #include #include #include #include #include #include #include #include #include #include #include #include "VZNL_Types.h" class DebugDataSaver { public: explicit DebugDataSaver(int maxThreads = 4); ~DebugDataSaver(); void SaveAsync(const std::string& basePath, const std::string& timestamp, const std::vector>& data, const std::string& deviceName = "", bool binary = false); 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); // 仅生成路径,让同次检测的图像与点云复用相同目录和文件前缀。 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 points3D; std::unique_ptr 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; // Streaming is fed by the cache worker. Enqueue must not wait for disk // throughput; the queue is drained when FinishStream joins the worker. std::queue> 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& task); std::mutex m_streamMutex; std::map> m_streams; struct SaveTask { std::string basePath; std::string filePath; std::string deviceName; bool binary = false; std::vector> data; }; struct StorageFileInfo { std::string path; int64_t size = 0; int64_t lastModifiedMSecs = 0; }; struct StorageCache { bool initialized = false; int64_t totalSize = 0; std::vector files; }; void WorkerThread(); static std::vector> DeepCopyData( const std::vector>& src); static void FreeData(std::vector>& data); void EnsureStorageLimit(const std::string& basePath); void EnsureStorageLimitLocked(const std::string& basePath, StorageCache& cache, const std::string& protectedPath = ""); 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); static bool RemoveFileFromCache(StorageCache& cache, const std::string& filePath); static bool IsOlderFile(const StorageFileInfo& lhs, const StorageFileInfo& rhs); static int64_t ComputeDirSize(const std::string& rootPath); static std::string FindOldestFile(const std::string& rootPath); int m_maxThreads; std::vector m_workers; std::queue m_taskQueue; std::mutex m_queueMutex; std::condition_variable m_queueCondition; std::atomic m_running; std::atomic m_maxQueueSize{10}; std::mutex m_diskSpaceMutex; std::atomic m_maxStorageSize{32LL * 1024 * 1024 * 1024}; std::map m_storageCaches; }; #endif // DEBUG_DATA_SAVER_H