/*** Oak - Non-Linear Video Editor Copyright (C) 2026 Oak Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef OAK_IPC_IPCMESSAGE_H #define OAK_IPC_IPCMESSAGE_H #include #include #include #include #include class QIODevice; namespace olive { namespace ipc { /** * @brief Control-plane protocol exchanged over stdio between main and render worker. * * The wire format is NDJSON: one compact QJsonObject per line, terminated by '\n'. This is * deliberately human-readable so the channel can be inspected live with `tee`/`cat` and test * messages can be injected by hand. The stdio channel carries only low-frequency control traffic; * bulk pixel data travels through the shared-memory FrameSlotPool, and the (potentially large) * serialized node graph travels via a temporary file referenced by path. * * Every message object has a "type" string field. Directionality (M = main, W = worker): * "handshake" M<->W Negotiate protocol version and announce shared-memory key/geometry. * "load_graph" M ->W Path to a temporary file holding the serialized node graph. * "render_frame" M ->W Request a frame: node uuid, time, video params. * "frame_ready" W ->M A rendered frame is published; carries the output-slot index + ticket. * "cancel" M ->W Abandon an in-flight ticket by id. * "graph_update" M ->W (Reserved, Phase 6) Incremental graph mutation, mirrors ProjectCopier. * "shutdown" M ->W Finish current work and exit cleanly. * "error" W ->M Worker-side failure report (human-readable "message" field). */ namespace msgtype { constexpr const char *k_handshake = "handshake"; constexpr const char *k_load_graph = "load_graph"; constexpr const char *k_render_frame = "render_frame"; constexpr const char *k_frame_ready = "frame_ready"; constexpr const char *k_cancel = "cancel"; constexpr const char *k_graph_update = "graph_update"; constexpr const char *k_shutdown = "shutdown"; constexpr const char *k_error = "error"; } // namespace msgtype /** * @brief Write one NDJSON message line to `device`. * * Serializes `obj` to compact JSON, appends '\n', and writes the whole line in one call. Returns * true only if the full line was written. */ bool write_message(QIODevice *device, const QJsonObject &obj); /** * @brief Pull one complete NDJSON line out of `buffer` and parse it. * * If `buffer` contains at least one '\n', the leading line is removed, parsed as JSON, and returned * via `out` (true). If no complete line is buffered yet, leaves `buffer` untouched and returns * false. Malformed lines are skipped (removed) and reported via `*ok = false` so the reader can log * and continue rather than wedge. Supports the typical "append bytes as they arrive, then drain * complete lines" reader loop on a pipe. */ bool read_message(QByteArray *buffer, QJsonObject *out, bool *ok = nullptr); // ---- Typed message builders / parsers ------------------------------------------------------- // // Thin helpers that construct or read the QJsonObject for each message type, keeping field names in // one place so main and worker agree. Fields use plain JSON numbers/strings; 64-bit ids are stored // as JSON numbers (doubles exactly represent integers up to 2^53, ample for our counters). struct HandshakeMsg { int protocol_version = 0; QString shm_key; ///< Worker->main output shared-memory segment key. QString input_shm_key; ///< Main->worker input shared-memory segment key (optional). int input_slots = 0; ///< Number of main->worker input frame slots. int output_slots = 0; ///< Number of worker->main output frame slots. qint64 slot_data_bytes = 0; ///< Per-output-slot pixel block size. qint64 input_slot_data_bytes = 0; ///< Per-input-slot pixel block size. QJsonObject to_json() const; static bool from_json(const QJsonObject &o, HandshakeMsg *out); }; struct RenderFrameMsg { qint64 ticket_id = 0; ///< Correlates this request with the eventual frame_ready. QString node_uuid; ///< Output/viewer node to render, by stable uuid in the loaded graph. qint64 time_num = 0; qint64 time_den = 1; int width = 0; ///< Forced output size (0 = use graph default). int height = 0; int format = -1; ///< Forced PixelFormat::Format (-1 = default/INVALID). int channel_count = 0; ///< 0 = default. int mode = 0; ///< RenderMode::Mode. int input_slot = -1; ///< Optional main->worker decoded input slot for footage nodes. QVector input_slots; ///< Optional ordered decoded input slots for footage nodes. // Output color transform to apply before returning the frame. When empty, // the worker returns the image in the project's reference space. bool has_color_transform = false; bool color_is_display = false; QString color_output; QString color_view; QString color_look; QJsonObject to_json() const; static bool from_json(const QJsonObject &o, RenderFrameMsg *out); }; struct FrameReadyMsg { qint64 ticket_id = 0; int output_slot = 0; ///< Index into the worker->main output FrameSlotPool. QJsonObject to_json() const; static bool from_json(const QJsonObject &o, FrameReadyMsg *out); }; struct CancelMsg { qint64 ticket_id = 0; QJsonObject to_json() const; static bool from_json(const QJsonObject &o, CancelMsg *out); }; struct LoadGraphMsg { QString path; ///< Temporary file holding the serialized node graph. QJsonObject to_json() const; static bool from_json(const QJsonObject &o, LoadGraphMsg *out); }; } // namespace ipc } // namespace olive #endif // OAK_IPC_IPCMESSAGE_H