Files
ts-qt/components/ffmsep/presist/presist.cc

187 lines
4.8 KiB
C++

//
// Created by Lenn on 2025/10/31.
//
#include "components/ffmsep/presist/presist.hh"
#include "components/ffmsep/cpstream_core.hh"
#include <chrono>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <system_error>
#include <nlohmann/json.hpp>
namespace ffmsep::persist {
namespace {
using nlohmann::json;
json serialize_tactile_frame(const DecodedFrame& frame) {
json result = {
{"pts", frame.pts},
{"raw_frame", frame.frame.data},
{"pressures", frame.tactile_pressures},
};
const auto received = frame.received_at.time_since_epoch();
result["received_at_ns"] =
std::chrono::duration_cast<std::chrono::nanoseconds>(received).count();
if (frame.tactile_matrix_size) {
result["matrix"] = {
{"long_edge", frame.tactile_matrix_size->long_edge},
{"short_edge", frame.tactile_matrix_size->short_edge},
};
}
if (frame.tactile) {
const auto& tactile = *frame.tactile;
result["tactile"] = {
{"device_address", tactile.device_address},
{"response_function", tactile.response_function},
{"function", static_cast<std::uint8_t>(tactile.function)},
{"start_address", tactile.start_address},
{"return_byte_count", tactile.return_byte_count},
{"status", tactile.status},
{"payload", tactile.payload},
};
}
return result;
}
} // namespace
bool WriteQueue::push(WriteRequest&& req) {
{
std::lock_guard<std::mutex> lock(mutex_);
if (stopped_) {
return false;
}
queue_.push(std::move(req));
}
cond_.notify_one();
return true;
}
bool WriteQueue::pop(WriteRequest& out) {
std::unique_lock lock(mutex_);
cond_.wait(lock, [&] {
return stopped_ || !queue_.empty();
});
if (queue_.empty()) {
return false;
}
out = std::move(queue_.front());
queue_.pop();
return true;
}
void WriteQueue::stop() {
{
std::lock_guard<std::mutex> lock(mutex_);
stopped_ = true;
}
cond_.notify_all();
}
JsonWritter::JsonWritter()
: write_thread_([this] { run(); }) {}
JsonWritter::~JsonWritter() {
stop();
}
std::future<WriteResult> JsonWritter::enqueue(std::string path,
std::deque<std::shared_ptr<DecodedFrame>> frames) {
std::promise<WriteResult> promise;
auto future = promise.get_future();
WriteRequest request{std::move(path), std::move(frames), std::move(promise)};
if (!write_queue_.push(std::move(request))) {
WriteResult result{false, "writer has been stopped", request.path};
request.promise.set_value(std::move(result));
}
return future;
}
void JsonWritter::run() {
WriteRequest request;
while (write_queue_.pop(request)) {
try {
auto result = write_once(request.path, std::move(request.frames));
request.promise.set_value(std::move(result));
} catch (const std::exception& ex) {
request.promise.set_value(WriteResult{false, ex.what(), request.path});
} catch (...) {
request.promise.set_value(WriteResult{false, "unknown error", request.path});
}
}
}
WriteResult JsonWritter::write_once(const std::string& path,
std::deque<std::shared_ptr<DecodedFrame>> frames) {
if (path.empty()) {
return {false, "export path is empty", path};
}
json tactile_frames = json::array();
for (const auto& frame : frames) {
if (!frame) {
continue;
}
if (frame->id != CPCodecID::Tactile || !frame->tactile) {
continue;
}
tactile_frames.push_back(serialize_tactile_frame(*frame));
}
if (tactile_frames.empty()) {
return {false, "no tactile frames available for export", path};
}
json root;
root["codec"] = "tactile";
root["frames"] = std::move(tactile_frames);
std::filesystem::path fs_path(path);
if (fs_path.has_parent_path()) {
std::error_code ec;
std::filesystem::create_directories(fs_path.parent_path(), ec);
if (ec) {
return {false, "failed to create export directory: " + ec.message(), path};
}
}
std::ofstream stream(path, std::ios::binary | std::ios::trunc);
if (!stream.is_open()) {
return {false, "failed to open export file", path};
}
stream << std::setw(2) << root;
stream.flush();
if (!stream.good()) {
return {false, "failed to write export file", path};
}
return {true, {}, path};
}
void JsonWritter::stop() {
if (!stopped_.exchange(true)) {
write_queue_.stop();
if (write_thread_.joinable()) {
write_thread_.join();
}
}
}
} // namespace ffmsep::persist