/***
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 .
***/
#include "oakengine/ipc.h"
#include
#include
#include
#include
#include
#include
#include
#include "oliveimpl/render/ipc/frameslotpool.h"
#include "oliveimpl/render/ipc/ipcmessage.h"
#include "oliveimpl/render/ipc/sharedmemoryregion.h"
namespace
{
namespace internal_ipc = olive::engine::internal::ipc;
olive::engine::internal::ipc::SharedMemoryRegion *impl(OakSharedMemoryRegion *h)
{
return reinterpret_cast(
h);
}
const olive::engine::internal::ipc::SharedMemoryRegion *
impl(const OakSharedMemoryRegion *h)
{
return reinterpret_cast<
const olive::engine::internal::ipc::SharedMemoryRegion *>(h);
}
OakSharedMemoryRegion *
wrap(olive::engine::internal::ipc::SharedMemoryRegion *r)
{
return reinterpret_cast(r);
}
olive::engine::internal::ipc::FrameSlotPool *impl(OakFrameSlotPool *h)
{
return reinterpret_cast(h);
}
const olive::engine::internal::ipc::FrameSlotPool *
impl(const OakFrameSlotPool *h)
{
return reinterpret_cast(
h);
}
OakFrameSlotPool *wrap(olive::engine::internal::ipc::FrameSlotPool *p)
{
return reinterpret_cast(p);
}
// Copy a QString into a fixed-capacity C buffer, always NUL-terminating and
// truncating what does not fit.
void copy_to_buf(const QString &s, char *dst, size_t cap)
{
const QByteArray utf = s.toUtf8();
const size_t n = qMin(size_t(utf.size()), cap - 1);
memcpy(dst, utf.constData(), n);
dst[n] = '\0';
}
// buf/size convention: returns the would-be length excluding the NUL.
int string_to_buf(const QString &s, char *buf, int buf_size)
{
const QByteArray utf = s.toUtf8();
if (buf && buf_size > 0) {
snprintf(buf, size_t(buf_size), "%s", utf.constData());
}
return int(utf.size());
}
int object_to_buf(const QJsonObject &o, char *buf, int buf_size)
{
const QByteArray json = QJsonDocument(o).toJson(QJsonDocument::Compact);
if (buf && buf_size > 0) {
snprintf(buf, size_t(buf_size), "%s", json.constData());
}
return int(json.size());
}
bool parse_object(const char *json, QJsonObject *out)
{
if (!json) {
return false;
}
QJsonParseError err;
const QJsonDocument doc = QJsonDocument::fromJson(json, &err);
if (err.error != QJsonParseError::NoError || !doc.isObject()) {
return false;
}
*out = doc.object();
return true;
}
// ---- POD <-> impl message conversions ------------------------------------
void to_c(const internal_ipc::HandshakeMsg &in, oak_ipc_handshake *out)
{
memset(out, 0, sizeof(*out));
out->protocol_version = in.protocol_version;
copy_to_buf(in.shm_key, out->shm_key, sizeof(out->shm_key));
copy_to_buf(in.input_shm_key, out->input_shm_key,
sizeof(out->input_shm_key));
out->input_slots = in.input_slots;
out->output_slots = in.output_slots;
out->slot_data_bytes = in.slot_data_bytes;
out->input_slot_data_bytes = in.input_slot_data_bytes;
}
void from_c(const oak_ipc_handshake *in, internal_ipc::HandshakeMsg *out)
{
out->protocol_version = in->protocol_version;
out->shm_key = QString::fromUtf8(in->shm_key);
out->input_shm_key = QString::fromUtf8(in->input_shm_key);
out->input_slots = in->input_slots;
out->output_slots = in->output_slots;
out->slot_data_bytes = in->slot_data_bytes;
out->input_slot_data_bytes = in->input_slot_data_bytes;
}
void to_c(const internal_ipc::RenderFrameMsg &in, oak_ipc_render_frame *out)
{
memset(out, 0, sizeof(*out));
out->ticket_id = in.ticket_id;
copy_to_buf(in.node_uuid, out->node_uuid, sizeof(out->node_uuid));
out->time_num = in.time_num;
out->time_den = in.time_den;
out->width = in.width;
out->height = in.height;
out->format = in.format;
out->channel_count = in.channel_count;
out->mode = in.mode;
out->input_slot = in.input_slot;
const int count = qMin(int(in.input_slots.size()), OAK_IPC_INPUT_SLOTS_CAP);
out->input_slot_count = count;
for (int i = 0; i < count; i++) {
out->input_slots[i] = in.input_slots.at(i);
}
out->has_color_transform = in.has_color_transform ? 1 : 0;
out->color_is_display = in.color_is_display ? 1 : 0;
copy_to_buf(in.color_output, out->color_output,
sizeof(out->color_output));
copy_to_buf(in.color_view, out->color_view, sizeof(out->color_view));
copy_to_buf(in.color_look, out->color_look, sizeof(out->color_look));
}
void from_c(const oak_ipc_render_frame *in, internal_ipc::RenderFrameMsg *out)
{
out->ticket_id = in->ticket_id;
out->node_uuid = QString::fromUtf8(in->node_uuid);
out->time_num = in->time_num;
out->time_den = in->time_den;
out->width = in->width;
out->height = in->height;
out->format = in->format;
out->channel_count = in->channel_count;
out->mode = in->mode;
out->input_slot = in->input_slot;
out->input_slots.clear();
const int count = qMin(in->input_slot_count, OAK_IPC_INPUT_SLOTS_CAP);
for (int i = 0; i < count; i++) {
out->input_slots.append(in->input_slots[i]);
}
out->has_color_transform = in->has_color_transform != 0;
out->color_is_display = in->color_is_display != 0;
out->color_output = QString::fromUtf8(in->color_output);
out->color_view = QString::fromUtf8(in->color_view);
out->color_look = QString::fromUtf8(in->color_look);
}
void to_c(const internal_ipc::FrameReadyMsg &in, oak_ipc_frame_ready *out)
{
memset(out, 0, sizeof(*out));
out->ticket_id = in.ticket_id;
out->output_slot = in.output_slot;
}
void from_c(const oak_ipc_frame_ready *in, internal_ipc::FrameReadyMsg *out)
{
out->ticket_id = in->ticket_id;
out->output_slot = in->output_slot;
}
void to_c(const internal_ipc::CancelMsg &in, oak_ipc_cancel *out)
{
memset(out, 0, sizeof(*out));
out->ticket_id = in.ticket_id;
}
void from_c(const oak_ipc_cancel *in, internal_ipc::CancelMsg *out)
{
out->ticket_id = in->ticket_id;
}
void to_c(const internal_ipc::LoadGraphMsg &in, oak_ipc_load_graph *out)
{
memset(out, 0, sizeof(*out));
copy_to_buf(in.path, out->path, sizeof(out->path));
}
void from_c(const oak_ipc_load_graph *in, internal_ipc::LoadGraphMsg *out)
{
out->path = QString::fromUtf8(in->path);
}
} // namespace
extern "C"
{
/* ---- SharedMemoryRegion ------------------------------------------------- */
OakSharedMemoryRegion *oakengine_ipc_shm_create(void)
{
return wrap(new internal_ipc::SharedMemoryRegion());
}
void oakengine_ipc_shm_free(OakSharedMemoryRegion *self)
{
delete impl(self);
}
int oakengine_ipc_shm_open(OakSharedMemoryRegion *self, const char *key,
size_t size, oak_ipc_shm_mode mode)
{
if (!self || !key) {
return 0;
}
const internal_ipc::SharedMemoryRegion::Mode m =
mode == OAK_IPC_SHM_MODE_CREATE ?
internal_ipc::SharedMemoryRegion::k_create :
internal_ipc::SharedMemoryRegion::k_attach;
return impl(self)->open(QString::fromUtf8(key), size, m) ? 1 : 0;
}
void oakengine_ipc_shm_close(OakSharedMemoryRegion *self)
{
if (self) {
impl(self)->close();
}
}
int oakengine_ipc_shm_is_valid(const OakSharedMemoryRegion *self)
{
return self && impl(self)->is_valid() ? 1 : 0;
}
void *oakengine_ipc_shm_data(OakSharedMemoryRegion *self)
{
return self ? impl(self)->data() : nullptr;
}
size_t oakengine_ipc_shm_size(const OakSharedMemoryRegion *self)
{
return self ? impl(self)->size() : 0;
}
int oakengine_ipc_shm_key(const OakSharedMemoryRegion *self, char *buf,
int buf_size)
{
return string_to_buf(self ? impl(self)->key() : QString(), buf, buf_size);
}
int oakengine_ipc_shm_error(const OakSharedMemoryRegion *self, char *buf,
int buf_size)
{
return string_to_buf(self ? impl(self)->error() : QString(), buf, buf_size);
}
int oakengine_ipc_shm_make_key(int64_t owner_pid, int worker_index, char *buf,
int buf_size)
{
return string_to_buf(
internal_ipc::SharedMemoryRegion::make_key(owner_pid, worker_index),
buf, buf_size);
}
/* ---- FrameSlotPool ------------------------------------------------------ */
size_t oakengine_ipc_framepool_bytes_needed(uint32_t slot_count,
size_t slot_data_bytes)
{
return internal_ipc::FrameSlotPool::bytes_needed(slot_count,
slot_data_bytes);
}
OakFrameSlotPool *oakengine_ipc_framepool_create(void *mem,
uint32_t slot_count,
size_t slot_data_bytes)
{
if (!mem) {
return nullptr;
}
return wrap(new internal_ipc::FrameSlotPool(
internal_ipc::FrameSlotPool::create(mem, slot_count,
slot_data_bytes)));
}
OakFrameSlotPool *oakengine_ipc_framepool_attach(void *mem)
{
if (!mem) {
return nullptr;
}
return wrap(new internal_ipc::FrameSlotPool(
internal_ipc::FrameSlotPool::attach(mem)));
}
OakFrameSlotPool *oakengine_ipc_framepool_copy(const OakFrameSlotPool *self)
{
if (!self) {
return nullptr;
}
return wrap(new internal_ipc::FrameSlotPool(*impl(self)));
}
void oakengine_ipc_framepool_free(OakFrameSlotPool *self)
{
delete impl(self);
}
int oakengine_ipc_framepool_is_valid(const OakFrameSlotPool *self)
{
return self && impl(self)->is_valid() ? 1 : 0;
}
uint32_t oakengine_ipc_framepool_slot_count(const OakFrameSlotPool *self)
{
return self ? impl(self)->slot_count() : 0;
}
size_t oakengine_ipc_framepool_slot_data_bytes(const OakFrameSlotPool *self)
{
return self ? impl(self)->slot_data_bytes() : 0;
}
int oakengine_ipc_framepool_acquire(OakFrameSlotPool *self, uint32_t *index)
{
return self && index && impl(self)->is_valid() &&
impl(self)->acquire(index) ?
1 :
0;
}
void *oakengine_ipc_framepool_slot_data(OakFrameSlotPool *self, uint32_t index)
{
return self && impl(self)->is_valid() ? impl(self)->slot_data(index) :
nullptr;
}
const void *oakengine_ipc_framepool_slot_data_const(
const OakFrameSlotPool *self, uint32_t index)
{
return self && impl(self)->is_valid() ? impl(self)->slot_data(index) :
nullptr;
}
oak_frame_slot_meta *oakengine_ipc_framepool_meta(OakFrameSlotPool *self,
uint32_t index)
{
return self && impl(self)->is_valid() ? impl(self)->meta(index) : nullptr;
}
const oak_frame_slot_meta *oakengine_ipc_framepool_meta_const(
const OakFrameSlotPool *self, uint32_t index)
{
return self && impl(self)->is_valid() ? impl(self)->meta(index) : nullptr;
}
int oakengine_ipc_framepool_publish(OakFrameSlotPool *self, uint32_t index)
{
return self && impl(self)->is_valid() && impl(self)->publish(index) ? 1 : 0;
}
int oakengine_ipc_framepool_consume(OakFrameSlotPool *self, uint32_t *index)
{
return self && index && impl(self)->is_valid() &&
impl(self)->consume(index) ?
1 :
0;
}
int oakengine_ipc_framepool_release(OakFrameSlotPool *self, uint32_t index)
{
return self && impl(self)->is_valid() && impl(self)->release(index) ? 1 : 0;
}
/* ---- Control-plane messages --------------------------------------------- */
oak_ipc_msgtype oakengine_ipc_message_type(const char *json)
{
QJsonObject o;
if (!parse_object(json, &o)) {
return OAK_IPC_MSGTYPE_UNKNOWN;
}
const QString type = o[QStringLiteral("type")].toString();
if (type == QLatin1String(internal_ipc::msgtype::k_handshake)) {
return OAK_IPC_MSGTYPE_HANDSHAKE;
}
if (type == QLatin1String(internal_ipc::msgtype::k_load_graph)) {
return OAK_IPC_MSGTYPE_LOAD_GRAPH;
}
if (type == QLatin1String(internal_ipc::msgtype::k_render_frame)) {
return OAK_IPC_MSGTYPE_RENDER_FRAME;
}
if (type == QLatin1String(internal_ipc::msgtype::k_frame_ready)) {
return OAK_IPC_MSGTYPE_FRAME_READY;
}
if (type == QLatin1String(internal_ipc::msgtype::k_cancel)) {
return OAK_IPC_MSGTYPE_CANCEL;
}
if (type == QLatin1String(internal_ipc::msgtype::k_graph_update)) {
return OAK_IPC_MSGTYPE_GRAPH_UPDATE;
}
if (type == QLatin1String(internal_ipc::msgtype::k_shutdown)) {
return OAK_IPC_MSGTYPE_SHUTDOWN;
}
if (type == QLatin1String(internal_ipc::msgtype::k_error)) {
return OAK_IPC_MSGTYPE_ERROR;
}
return OAK_IPC_MSGTYPE_UNKNOWN;
}
int oakengine_ipc_handshake_to_json(const oak_ipc_handshake *self, char *buf,
int buf_size)
{
if (!self) {
return -1;
}
internal_ipc::HandshakeMsg in;
from_c(self, &in);
return object_to_buf(in.to_json(), buf, buf_size);
}
int oakengine_ipc_handshake_parse(const char *json, oak_ipc_handshake *out)
{
if (!out) {
return 0;
}
QJsonObject o;
internal_ipc::HandshakeMsg in;
if (!parse_object(json, &o) ||
!internal_ipc::HandshakeMsg::from_json(o, &in)) {
return 0;
}
to_c(in, out);
return 1;
}
int oakengine_ipc_render_frame_to_json(const oak_ipc_render_frame *self,
char *buf, int buf_size)
{
if (!self) {
return -1;
}
internal_ipc::RenderFrameMsg in;
from_c(self, &in);
return object_to_buf(in.to_json(), buf, buf_size);
}
int oakengine_ipc_render_frame_parse(const char *json,
oak_ipc_render_frame *out)
{
if (!out) {
return 0;
}
QJsonObject o;
internal_ipc::RenderFrameMsg in;
if (!parse_object(json, &o) ||
!internal_ipc::RenderFrameMsg::from_json(o, &in)) {
return 0;
}
to_c(in, out);
return 1;
}
int oakengine_ipc_frame_ready_to_json(const oak_ipc_frame_ready *self,
char *buf, int buf_size)
{
if (!self) {
return -1;
}
internal_ipc::FrameReadyMsg in;
from_c(self, &in);
return object_to_buf(in.to_json(), buf, buf_size);
}
int oakengine_ipc_frame_ready_parse(const char *json,
oak_ipc_frame_ready *out)
{
if (!out) {
return 0;
}
QJsonObject o;
internal_ipc::FrameReadyMsg in;
if (!parse_object(json, &o) ||
!internal_ipc::FrameReadyMsg::from_json(o, &in)) {
return 0;
}
to_c(in, out);
return 1;
}
int oakengine_ipc_cancel_to_json(const oak_ipc_cancel *self, char *buf,
int buf_size)
{
if (!self) {
return -1;
}
internal_ipc::CancelMsg in;
from_c(self, &in);
return object_to_buf(in.to_json(), buf, buf_size);
}
int oakengine_ipc_cancel_parse(const char *json, oak_ipc_cancel *out)
{
if (!out) {
return 0;
}
QJsonObject o;
internal_ipc::CancelMsg in;
if (!parse_object(json, &o) ||
!internal_ipc::CancelMsg::from_json(o, &in)) {
return 0;
}
to_c(in, out);
return 1;
}
int oakengine_ipc_load_graph_to_json(const oak_ipc_load_graph *self, char *buf,
int buf_size)
{
if (!self) {
return -1;
}
internal_ipc::LoadGraphMsg in;
from_c(self, &in);
return object_to_buf(in.to_json(), buf, buf_size);
}
int oakengine_ipc_load_graph_parse(const char *json, oak_ipc_load_graph *out)
{
if (!out) {
return 0;
}
QJsonObject o;
internal_ipc::LoadGraphMsg in;
if (!parse_object(json, &o) ||
!internal_ipc::LoadGraphMsg::from_json(o, &in)) {
return 0;
}
to_c(in, out);
return 1;
}
int oakengine_ipc_shutdown_to_json(char *buf, int buf_size)
{
QJsonObject o;
o[QStringLiteral("type")] = internal_ipc::msgtype::k_shutdown;
return object_to_buf(o, buf, buf_size);
}
int oakengine_ipc_shutdown_parse(const char *json)
{
QJsonObject o;
if (!parse_object(json, &o)) {
return 0;
}
return o[QStringLiteral("type")].toString() ==
QLatin1String(internal_ipc::msgtype::k_shutdown) ?
1 :
0;
}
int oakengine_ipc_error_to_json(const char *message, char *buf, int buf_size)
{
QJsonObject o;
o[QStringLiteral("type")] = internal_ipc::msgtype::k_error;
o[QStringLiteral("message")] = QString::fromUtf8(message ? message : "");
return object_to_buf(o, buf, buf_size);
}
int oakengine_ipc_error_parse(const char *json, char *message_buf,
int message_buf_size)
{
QJsonObject o;
if (!parse_object(json, &o) ||
o[QStringLiteral("type")].toString() !=
QLatin1String(internal_ipc::msgtype::k_error)) {
return 0;
}
if (message_buf && message_buf_size > 0) {
copy_to_buf(o[QStringLiteral("message")].toString(), message_buf,
size_t(message_buf_size));
}
return 1;
}
} // extern "C"