feat: scaffold project framework with engine, node skeleton, and passthrough node
- CMake build system with vcpkg dependency management - libdmf-node: Node interface, port definitions, WebSocket control server, node runner with MXL lifecycle management - libdmf-engine: Graph model (nodes/edges CRUD, serialization), FlowManager (UUID + V210 NMOS flow definitions), ProcessManager (fork/exec node processes), ApiServer (REST API for graph control) - Passthrough node: reads V210 grains from MXL, copies, writes to MXL - Unit tests for Graph and FlowManager (6 cases, 21 assertions) - MXL SDK as git submodule (symlinked from extern/mxl)
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
#include "passthrough_node.hpp"
|
||||
|
||||
#include <mxl/flow.h>
|
||||
#include <mxl/mxl.h>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace dmf_node {
|
||||
|
||||
void PassthroughNode::on_add_writer(const std::string& port_id, mxlFlowWriter writer) {
|
||||
if (port_id == "video_out") {
|
||||
writer_ = writer;
|
||||
spdlog::info("Passthrough: writer added on video_out");
|
||||
}
|
||||
}
|
||||
|
||||
void PassthroughNode::on_add_reader(const std::string& port_id, mxlFlowReader reader) {
|
||||
if (port_id == "video_in") {
|
||||
reader_ = reader;
|
||||
spdlog::info("Passthrough: reader added on video_in");
|
||||
}
|
||||
}
|
||||
|
||||
void PassthroughNode::on_remove_writer(const std::string& port_id) {
|
||||
if (port_id == "video_out") {
|
||||
writer_.reset();
|
||||
spdlog::info("Passthrough: writer removed from video_out");
|
||||
}
|
||||
}
|
||||
|
||||
void PassthroughNode::on_remove_reader(const std::string& port_id) {
|
||||
if (port_id == "video_in") {
|
||||
reader_.reset();
|
||||
spdlog::info("Passthrough: reader removed from video_in");
|
||||
}
|
||||
}
|
||||
|
||||
void PassthroughNode::process() {
|
||||
if (!reader_ || !writer_) {
|
||||
return;
|
||||
}
|
||||
|
||||
mxlGrainInfo grain_info{};
|
||||
uint8_t* payload = nullptr;
|
||||
|
||||
auto status = mxlFlowReaderGetGrain(*reader_, read_index_, 100000000ULL, &grain_info, &payload);
|
||||
if (status != MXL_STATUS_OK) {
|
||||
if (status == MXL_ERR_TIMEOUT) {
|
||||
return;
|
||||
}
|
||||
if (status == MXL_ERR_OUT_OF_RANGE_TOO_LATE) {
|
||||
mxlFlowRuntimeInfo runtime{};
|
||||
mxlFlowReaderGetRuntimeInfo(*reader_, &runtime);
|
||||
read_index_ = runtime.headIndex;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
mxlGrainInfo out_grain{};
|
||||
uint8_t* out_payload = nullptr;
|
||||
status = mxlFlowWriterOpenGrain(*writer_, write_index_, &out_grain, &out_payload);
|
||||
if (status != MXL_STATUS_OK) {
|
||||
spdlog::warn("Passthrough: failed to open output grain at index {}: {}", write_index_, static_cast<int>(status));
|
||||
read_index_++;
|
||||
return;
|
||||
}
|
||||
|
||||
auto copy_size = std::min(grain_info.grainSize, out_grain.grainSize);
|
||||
std::memcpy(out_payload, payload, copy_size);
|
||||
|
||||
out_grain.validSlices = grain_info.validSlices;
|
||||
out_grain.flags = grain_info.flags;
|
||||
mxlFlowWriterCommitGrain(*writer_, &out_grain);
|
||||
|
||||
read_index_++;
|
||||
write_index_++;
|
||||
grains_processed_++;
|
||||
}
|
||||
|
||||
nlohmann::json PassthroughNode::status() const {
|
||||
return {
|
||||
{"type", "passthrough"},
|
||||
{"grains_processed", grains_processed_},
|
||||
{"read_index", read_index_},
|
||||
{"write_index", write_index_},
|
||||
{"has_reader", reader_.has_value()},
|
||||
{"has_writer", writer_.has_value()},
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace dmf_node
|
||||
Reference in New Issue
Block a user