#pragma once #include #include #include #include #include #include #include "Signal.hpp" namespace dmf { inline const char* mxl_status_str(mxlStatus s) noexcept { switch (s) { case MXL_STATUS_OK: return "MXL_STATUS_OK"; case MXL_ERR_UNKNOWN: return "MXL_ERR_UNKNOWN"; case MXL_ERR_FLOW_NOT_FOUND: return "MXL_ERR_FLOW_NOT_FOUND"; case MXL_ERR_OUT_OF_RANGE_TOO_LATE: return "MXL_ERR_OUT_OF_RANGE_TOO_LATE"; case MXL_ERR_OUT_OF_RANGE_TOO_EARLY: return "MXL_ERR_OUT_OF_RANGE_TOO_EARLY"; case MXL_ERR_INVALID_FLOW_READER: return "MXL_ERR_INVALID_FLOW_READER"; case MXL_ERR_INVALID_FLOW_WRITER: return "MXL_ERR_INVALID_FLOW_WRITER"; case MXL_ERR_TIMEOUT: return "MXL_ERR_TIMEOUT"; case MXL_ERR_INVALID_ARG: return "MXL_ERR_INVALID_ARG"; case MXL_ERR_CONFLICT: return "MXL_ERR_CONFLICT"; case MXL_ERR_PERMISSION_DENIED: return "MXL_ERR_PERMISSION_DENIED"; case MXL_ERR_FLOW_INVALID: return "MXL_ERR_FLOW_INVALID"; case MXL_ERR_STRLEN: return "MXL_ERR_STRLEN"; case MXL_ERR_INTERRUPTED: return "MXL_ERR_INTERRUPTED"; case MXL_ERR_NO_FABRIC: return "MXL_ERR_NO_FABRIC"; case MXL_ERR_INVALID_STATE: return "MXL_ERR_INVALID_STATE"; case MXL_ERR_INTERNAL: return "MXL_ERR_INTERNAL"; case MXL_ERR_NOT_READY: return "MXL_ERR_NOT_READY"; case MXL_ERR_NOT_FOUND: return "MXL_ERR_NOT_FOUND"; case MXL_ERR_EXISTS: return "MXL_ERR_EXISTS"; case MXL_ERR_UNSUPPORTED_OPERATION: return "MXL_ERR_UNSUPPORTED_OPERATION"; default: return "MXL_ERR_UNRECOGNIZED"; } } // Base class for all DMF node binaries. // // Handles the boilerplate every node needs: // - NODE_CONFIG env var → parsed JSON config // - MXL_DOMAIN env var → domain path // - mxlCreateInstance / mxlDestroyInstance lifecycle // - Signal handler installation // - [node_id]-prefixed logging // // Derived class implements only run(), which receives a valid MXL instance // and runs until dmf::g_running is false (set by SIGTERM/SIGINT). class NodeBase { public: virtual ~NodeBase() = default; // Entry point for main(). Returns 0 on success, 1 on error. int execute() { install_signal_handlers(); const char* config_env = std::getenv("NODE_CONFIG"); if (!config_env) { fprintf(stderr, "[node] NODE_CONFIG not set\n"); return 1; } cfg_ = nlohmann::json::parse(config_env, nullptr, /*allow_exceptions=*/false); if (cfg_.is_discarded()) { fprintf(stderr, "[node] NODE_CONFIG is not valid JSON\n"); return 1; } node_id_ = cfg_.value("node_id", std::string("node")); const char* domain_env = std::getenv("MXL_DOMAIN"); #ifdef __APPLE__ domain_ = domain_env ? domain_env : "/tmp/dmf-studio"; #else domain_ = domain_env ? domain_env : "/dev/shm/dmf-studio"; #endif log("domain=%s", domain_.c_str()); inst_ = mxlCreateInstance(domain_.c_str(), ""); if (!inst_) { log("mxlCreateInstance failed at %s", domain_.c_str()); return 1; } run(); mxlDestroyInstance(inst_); inst_ = nullptr; return 0; } protected: // Implement the node's processing loop here. // Create MXL writers/readers, loop while dmf::g_running, release them before returning. virtual void run() = 0; const nlohmann::json& config() const { return cfg_; } mxlInstance instance() const { return inst_; } const std::string& node_id() const { return node_id_; } const std::string& domain() const { return domain_; } // Printf-style log with automatic "[node_id] " prefix and trailing newline. #if defined(__GNUC__) || defined(__clang__) __attribute__((format(printf, 2, 3))) #endif void log(const char* fmt, ...) const { fprintf(stderr, "[%s] ", node_id_.c_str()); va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); fputc('\n', stderr); } private: nlohmann::json cfg_; mxlInstance inst_{nullptr}; std::string node_id_{"node"}; std::string domain_; }; } // namespace dmf