engine: begin the liboakengine C ABI facade with the IPC subsystem
- oakengine/export.h establishes the OAKENGINE_API visibility macros; include/oakengine/ipc.h is the first pure-C surface (41 functions: shm, frame slot pool, and the worker IPC messages as POD<->JSON build/parse), implemented in engine/src/capi/ - the IPC implementations move to engine/src/oliveimpl (namespace olive::engine::internal::ipc); engine/render/ipc/*.h are rebuilt as same-name/same-API wrapper classes forwarding across the C boundary - FrameSlotMeta is shared with the C header verbatim so the app/worker wire format (v1) is bit-identical; static_asserts pin sizeof and field offsets - spscringbuffer.h moves to include/oakengine/ as an inline-only header (no symbols, not ABI) - new pure-C test oakengine_ipc_test (make_oakengine_test, no GL) covers shm, frame pool, message round-trips and the layout asserts; full gtest suite stays green (1986 tests)
This commit is contained in:
@@ -0,0 +1,591 @@
|
||||
/***
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
// Pure C ABI test for the liboakengine render/ipc facade. Exercises the
|
||||
// shared-memory region, the frame slot pool (including its version-1 wire
|
||||
// layout) and every control-plane message build/parse pair. No Qt, no GL.
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <process.h>
|
||||
#define getpid _getpid
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "oakengine/ipc.h"
|
||||
|
||||
// ---- Version-1 wire layout: oak_frame_slot_meta must never move ----------
|
||||
static_assert(sizeof(oak_frame_slot_meta) == 176, "FrameSlotMeta size");
|
||||
static_assert(offsetof(oak_frame_slot_meta, id) == 0, "id offset");
|
||||
static_assert(offsetof(oak_frame_slot_meta, time_num) == 8, "time_num offset");
|
||||
static_assert(offsetof(oak_frame_slot_meta, time_den) == 16, "time_den offset");
|
||||
static_assert(offsetof(oak_frame_slot_meta, width) == 24, "width offset");
|
||||
static_assert(offsetof(oak_frame_slot_meta, height) == 28, "height offset");
|
||||
static_assert(offsetof(oak_frame_slot_meta, format) == 32, "format offset");
|
||||
static_assert(offsetof(oak_frame_slot_meta, channel_count) == 36,
|
||||
"channel_count offset");
|
||||
static_assert(offsetof(oak_frame_slot_meta, linesize) == 40, "linesize offset");
|
||||
static_assert(offsetof(oak_frame_slot_meta, data_size) == 44,
|
||||
"data_size offset");
|
||||
static_assert(offsetof(oak_frame_slot_meta, colorspace) == 48,
|
||||
"colorspace offset");
|
||||
|
||||
static void assert_json_eq(const char *produced, const char *expected)
|
||||
{
|
||||
if (strcmp(produced, expected) != 0) {
|
||||
fprintf(stderr, "JSON mismatch:\n produced: %s\n expected: %s\n",
|
||||
produced, expected);
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_shm(void)
|
||||
{
|
||||
// make_key: buf/size convention and exact format
|
||||
char key[OAK_IPC_SHM_KEY_CAP];
|
||||
const int64_t pid = getpid();
|
||||
int needed = oakengine_ipc_shm_make_key(pid, 0, NULL, 0);
|
||||
char expected[64];
|
||||
snprintf(expected, sizeof(expected), "olive-rw-%lld-0", (long long)pid);
|
||||
assert(needed == (int)strlen(expected));
|
||||
assert(oakengine_ipc_shm_make_key(pid, 0, key, sizeof(key)) == needed);
|
||||
assert(strcmp(key, expected) == 0);
|
||||
assert(oakengine_ipc_shm_make_key(pid, 0, key, 6) == needed); // truncated
|
||||
assert(strcmp(key, "olive") == 0);
|
||||
// A different worker index produces a different key.
|
||||
char key1[OAK_IPC_SHM_KEY_CAP];
|
||||
assert(oakengine_ipc_shm_make_key(pid, 1, key1, sizeof(key1)) == needed);
|
||||
assert(strcmp(key1, expected) != 0);
|
||||
|
||||
const int key_len = oakengine_ipc_shm_make_key(pid, 0, key, sizeof(key));
|
||||
(void)key_len;
|
||||
|
||||
// Attaching to a missing segment fails and reports an error.
|
||||
OakSharedMemoryRegion *missing = oakengine_ipc_shm_create();
|
||||
assert(oakengine_ipc_shm_open(missing, key, 4096,
|
||||
OAK_IPC_SHM_MODE_ATTACH) == 0);
|
||||
assert(oakengine_ipc_shm_is_valid(missing) == 0);
|
||||
char err[256];
|
||||
assert(oakengine_ipc_shm_error(missing, err, sizeof(err)) > 0);
|
||||
assert(strlen(err) > 0);
|
||||
oakengine_ipc_shm_free(missing);
|
||||
|
||||
// Create the owner: valid, sized, zero-initialized.
|
||||
OakSharedMemoryRegion *owner = oakengine_ipc_shm_create();
|
||||
assert(oakengine_ipc_shm_open(owner, key, 8192, OAK_IPC_SHM_MODE_CREATE) ==
|
||||
1);
|
||||
assert(oakengine_ipc_shm_is_valid(owner) == 1);
|
||||
assert(oakengine_ipc_shm_size(owner) == 8192);
|
||||
unsigned char *data = (unsigned char *)oakengine_ipc_shm_data(owner);
|
||||
assert(data != NULL);
|
||||
for (int i = 0; i < 8192; i++) {
|
||||
assert(data[i] == 0);
|
||||
}
|
||||
|
||||
// key() round-trips the opened key.
|
||||
char opened_key[OAK_IPC_SHM_KEY_CAP];
|
||||
assert(oakengine_ipc_shm_key(owner, opened_key, sizeof(opened_key)) ==
|
||||
key_len);
|
||||
assert(strcmp(opened_key, key) == 0);
|
||||
|
||||
// A peer attaches by the same key and sees the owner's bytes.
|
||||
data[0] = 0xAB;
|
||||
data[8191] = 0xCD;
|
||||
OakSharedMemoryRegion *peer = oakengine_ipc_shm_create();
|
||||
assert(oakengine_ipc_shm_open(peer, key, 8192, OAK_IPC_SHM_MODE_ATTACH) ==
|
||||
1);
|
||||
assert(oakengine_ipc_shm_size(peer) == 8192);
|
||||
const unsigned char *peer_data =
|
||||
(const unsigned char *)oakengine_ipc_shm_data(peer);
|
||||
assert(peer_data[0] == 0xAB && peer_data[8191] == 0xCD);
|
||||
peer_data = NULL;
|
||||
|
||||
// The peer closes without unlinking; the owner stays valid.
|
||||
oakengine_ipc_shm_close(peer);
|
||||
assert(oakengine_ipc_shm_is_valid(peer) == 0);
|
||||
assert(oakengine_ipc_shm_is_valid(owner) == 1);
|
||||
oakengine_ipc_shm_free(peer);
|
||||
|
||||
// The owner unlinks on close: a late attach must fail.
|
||||
oakengine_ipc_shm_close(owner);
|
||||
assert(oakengine_ipc_shm_is_valid(owner) == 0);
|
||||
OakSharedMemoryRegion *late = oakengine_ipc_shm_create();
|
||||
assert(oakengine_ipc_shm_open(late, key, 8192, OAK_IPC_SHM_MODE_ATTACH) ==
|
||||
0);
|
||||
oakengine_ipc_shm_free(late);
|
||||
oakengine_ipc_shm_free(owner);
|
||||
|
||||
// NULL safety.
|
||||
oakengine_ipc_shm_close(NULL);
|
||||
assert(oakengine_ipc_shm_is_valid(NULL) == 0);
|
||||
assert(oakengine_ipc_shm_data(NULL) == NULL);
|
||||
assert(oakengine_ipc_shm_size(NULL) == 0);
|
||||
oakengine_ipc_shm_free(NULL);
|
||||
}
|
||||
|
||||
static void test_framepool(void)
|
||||
{
|
||||
const uint32_t k_slots = 3;
|
||||
const size_t k_slot_bytes = 256;
|
||||
|
||||
// bytes_needed stays 64-aligned and grows with geometry.
|
||||
assert(oakengine_ipc_framepool_bytes_needed(k_slots, k_slot_bytes) % 64 ==
|
||||
0);
|
||||
assert(oakengine_ipc_framepool_bytes_needed(2, 65) ==
|
||||
oakengine_ipc_framepool_bytes_needed(2, 128));
|
||||
assert(oakengine_ipc_framepool_bytes_needed(1, 64) <
|
||||
oakengine_ipc_framepool_bytes_needed(2, 64));
|
||||
|
||||
const size_t bytes =
|
||||
oakengine_ipc_framepool_bytes_needed(k_slots, k_slot_bytes);
|
||||
void *mem = malloc(bytes);
|
||||
assert(mem != NULL);
|
||||
|
||||
OakFrameSlotPool *filler =
|
||||
oakengine_ipc_framepool_create(mem, k_slots, k_slot_bytes);
|
||||
assert(filler != NULL);
|
||||
assert(oakengine_ipc_framepool_is_valid(filler) == 1);
|
||||
assert(oakengine_ipc_framepool_slot_count(filler) == k_slots);
|
||||
assert(oakengine_ipc_framepool_slot_data_bytes(filler) == k_slot_bytes);
|
||||
|
||||
// Peer maps the same memory; a copy of the handle views it too.
|
||||
OakFrameSlotPool *drainer = oakengine_ipc_framepool_attach(mem);
|
||||
assert(oakengine_ipc_framepool_is_valid(drainer) == 1);
|
||||
OakFrameSlotPool *drainer_copy = oakengine_ipc_framepool_copy(drainer);
|
||||
assert(drainer_copy != NULL && drainer_copy != drainer);
|
||||
assert(oakengine_ipc_framepool_slot_count(drainer_copy) == k_slots);
|
||||
|
||||
// Free slots are issued FIFO; the pool then reports exhausted.
|
||||
uint32_t held[3];
|
||||
for (uint32_t i = 0; i < k_slots; i++) {
|
||||
assert(oakengine_ipc_framepool_acquire(filler, &held[i]) == 1);
|
||||
assert(held[i] == i);
|
||||
}
|
||||
uint32_t overflow = 99;
|
||||
assert(oakengine_ipc_framepool_acquire(filler, &overflow) == 0);
|
||||
assert(overflow == 99);
|
||||
|
||||
// Fill slot 0 with a pattern + full metadata and publish it.
|
||||
unsigned char *data =
|
||||
(unsigned char *)oakengine_ipc_framepool_slot_data(filler, held[0]);
|
||||
assert(data != NULL);
|
||||
for (size_t i = 0; i < k_slot_bytes; i++) {
|
||||
data[i] = (unsigned char)(i & 0xFF);
|
||||
}
|
||||
oak_frame_slot_meta *meta = oakengine_ipc_framepool_meta(filler, held[0]);
|
||||
assert(meta != NULL);
|
||||
meta->id = -4242;
|
||||
meta->time_num = 1001;
|
||||
meta->time_den = 30000;
|
||||
meta->width = 3840;
|
||||
meta->height = 2160;
|
||||
meta->format = 17;
|
||||
meta->channel_count = 4;
|
||||
meta->linesize = 3840 * 4 * 4;
|
||||
meta->data_size = (int32_t)k_slot_bytes;
|
||||
strncpy(meta->colorspace, "acescg", sizeof(meta->colorspace) - 1);
|
||||
meta->colorspace[sizeof(meta->colorspace) - 1] = '\0';
|
||||
assert(oakengine_ipc_framepool_publish(filler, held[0]) == 1);
|
||||
|
||||
// Publish slot 2 then slot 1: consume order follows publish order.
|
||||
assert(oakengine_ipc_framepool_publish(filler, held[2]) == 1);
|
||||
assert(oakengine_ipc_framepool_publish(filler, held[1]) == 1);
|
||||
|
||||
uint32_t got = 99;
|
||||
assert(oakengine_ipc_framepool_consume(drainer, &got) == 1);
|
||||
assert(got == held[0]);
|
||||
|
||||
// Metadata and pixels arrive intact on the drainer side.
|
||||
const oak_frame_slot_meta *out =
|
||||
oakengine_ipc_framepool_meta_const(drainer, got);
|
||||
assert(out != NULL);
|
||||
assert(out->id == -4242);
|
||||
assert(out->time_num == 1001 && out->time_den == 30000);
|
||||
assert(out->width == 3840 && out->height == 2160);
|
||||
assert(out->format == 17 && out->channel_count == 4);
|
||||
assert(out->linesize == 3840 * 4 * 4);
|
||||
assert(out->data_size == (int32_t)k_slot_bytes);
|
||||
assert(strcmp(out->colorspace, "acescg") == 0);
|
||||
const unsigned char *out_data =
|
||||
(const unsigned char *)oakengine_ipc_framepool_slot_data_const(drainer,
|
||||
got);
|
||||
assert(out_data != NULL);
|
||||
for (size_t i = 0; i < k_slot_bytes; i++) {
|
||||
assert(out_data[i] == (unsigned char)(i & 0xFF));
|
||||
}
|
||||
assert(oakengine_ipc_framepool_release(drainer, got) == 1);
|
||||
|
||||
// The copied handle consumes the remaining slots in publish order.
|
||||
assert(oakengine_ipc_framepool_consume(drainer_copy, &got) == 1);
|
||||
assert(got == held[2]);
|
||||
assert(oakengine_ipc_framepool_release(drainer_copy, got) == 1);
|
||||
assert(oakengine_ipc_framepool_consume(drainer_copy, &got) == 1);
|
||||
assert(got == held[1]);
|
||||
assert(oakengine_ipc_framepool_release(drainer_copy, got) == 1);
|
||||
assert(oakengine_ipc_framepool_consume(drainer, &got) == 0); // drained
|
||||
|
||||
// Released slots cycle back to the filler.
|
||||
uint32_t again = 0;
|
||||
assert(oakengine_ipc_framepool_acquire(filler, &again) == 1);
|
||||
|
||||
oakengine_ipc_framepool_free(drainer_copy);
|
||||
oakengine_ipc_framepool_free(drainer);
|
||||
oakengine_ipc_framepool_free(filler);
|
||||
free(mem);
|
||||
|
||||
// A region without the pool magic attaches as invalid.
|
||||
void *raw = calloc(1, oakengine_ipc_framepool_bytes_needed(2, 64));
|
||||
assert(raw != NULL);
|
||||
OakFrameSlotPool *bad = oakengine_ipc_framepool_attach(raw);
|
||||
assert(bad != NULL);
|
||||
assert(oakengine_ipc_framepool_is_valid(bad) == 0);
|
||||
assert(oakengine_ipc_framepool_slot_count(bad) == 0);
|
||||
assert(oakengine_ipc_framepool_slot_data_bytes(bad) == 0);
|
||||
oakengine_ipc_framepool_free(bad);
|
||||
free(raw);
|
||||
|
||||
// NULL safety.
|
||||
assert(oakengine_ipc_framepool_is_valid(NULL) == 0);
|
||||
assert(oakengine_ipc_framepool_slot_count(NULL) == 0);
|
||||
assert(oakengine_ipc_framepool_slot_data(NULL, 0) == NULL);
|
||||
assert(oakengine_ipc_framepool_meta(NULL, 0) == NULL);
|
||||
assert(oakengine_ipc_framepool_acquire(NULL, &again) == 0);
|
||||
oakengine_ipc_framepool_free(NULL);
|
||||
}
|
||||
|
||||
// Frame hand-off across two live shared-memory mappings, the app<->worker shape.
|
||||
static void test_framepool_over_shm(void)
|
||||
{
|
||||
char key[OAK_IPC_SHM_KEY_CAP];
|
||||
assert(oakengine_ipc_shm_make_key(getpid(), 7, key, sizeof(key)) > 0);
|
||||
|
||||
const uint32_t k_slots = 2;
|
||||
const size_t k_slot_bytes = 64;
|
||||
const size_t bytes =
|
||||
oakengine_ipc_framepool_bytes_needed(k_slots, k_slot_bytes);
|
||||
|
||||
OakSharedMemoryRegion *owner = oakengine_ipc_shm_create();
|
||||
assert(oakengine_ipc_shm_open(owner, key, bytes, OAK_IPC_SHM_MODE_CREATE) ==
|
||||
1);
|
||||
OakSharedMemoryRegion *peer = oakengine_ipc_shm_create();
|
||||
assert(oakengine_ipc_shm_open(peer, key, bytes, OAK_IPC_SHM_MODE_ATTACH) ==
|
||||
1);
|
||||
|
||||
OakFrameSlotPool *filler = oakengine_ipc_framepool_create(
|
||||
oakengine_ipc_shm_data(owner), k_slots, k_slot_bytes);
|
||||
OakFrameSlotPool *drainer = oakengine_ipc_framepool_attach(
|
||||
oakengine_ipc_shm_data(peer));
|
||||
assert(oakengine_ipc_framepool_is_valid(filler) == 1);
|
||||
assert(oakengine_ipc_framepool_is_valid(drainer) == 1);
|
||||
|
||||
uint32_t idx = 0;
|
||||
assert(oakengine_ipc_framepool_acquire(filler, &idx) == 1);
|
||||
oak_frame_slot_meta *meta = oakengine_ipc_framepool_meta(filler, idx);
|
||||
meta->id = 77;
|
||||
meta->data_size = (int32_t)k_slot_bytes;
|
||||
memset(oakengine_ipc_framepool_slot_data(filler, idx), 0x5A, k_slot_bytes);
|
||||
assert(oakengine_ipc_framepool_publish(filler, idx) == 1);
|
||||
|
||||
uint32_t got = 0;
|
||||
assert(oakengine_ipc_framepool_consume(drainer, &got) == 1);
|
||||
assert(got == idx);
|
||||
assert(oakengine_ipc_framepool_meta_const(drainer, got)->id == 77);
|
||||
const unsigned char *d = (const unsigned char *)
|
||||
oakengine_ipc_framepool_slot_data_const(drainer, got);
|
||||
assert(d[0] == 0x5A && d[k_slot_bytes - 1] == 0x5A);
|
||||
assert(oakengine_ipc_framepool_release(drainer, got) == 1);
|
||||
|
||||
oakengine_ipc_framepool_free(drainer);
|
||||
oakengine_ipc_framepool_free(filler);
|
||||
oakengine_ipc_shm_free(peer);
|
||||
oakengine_ipc_shm_free(owner); // owner frees last: unlinks the segment
|
||||
}
|
||||
|
||||
static void test_handshake(void)
|
||||
{
|
||||
oak_ipc_handshake hs;
|
||||
memset(&hs, 0, sizeof(hs));
|
||||
hs.protocol_version = 1;
|
||||
strcpy(hs.shm_key, "olive-rw-1234-0-out");
|
||||
strcpy(hs.input_shm_key, "olive-rw-1234-1-in");
|
||||
hs.input_slots = 4;
|
||||
hs.output_slots = 6;
|
||||
hs.slot_data_bytes = 256ll * 1024 * 1024;
|
||||
hs.input_slot_data_bytes = 128ll * 1024 * 1024;
|
||||
|
||||
const int needed = oakengine_ipc_handshake_to_json(&hs, NULL, 0);
|
||||
assert(needed > 0);
|
||||
char *json = (char *)malloc(size_t(needed) + 1);
|
||||
assert(json != NULL);
|
||||
assert(oakengine_ipc_handshake_to_json(&hs, json, needed + 1) == needed);
|
||||
assert((int)strlen(json) == needed);
|
||||
|
||||
// buf/size convention: a short buffer truncates but reports the full size.
|
||||
char tiny[8];
|
||||
assert(oakengine_ipc_handshake_to_json(&hs, tiny, sizeof(tiny)) == needed);
|
||||
assert(strlen(tiny) == sizeof(tiny) - 1);
|
||||
|
||||
assert(oakengine_ipc_message_type(json) == OAK_IPC_MSGTYPE_HANDSHAKE);
|
||||
|
||||
oak_ipc_handshake back;
|
||||
assert(oakengine_ipc_handshake_parse(json, &back) == 1);
|
||||
assert(back.protocol_version == hs.protocol_version);
|
||||
assert(strcmp(back.shm_key, hs.shm_key) == 0);
|
||||
assert(strcmp(back.input_shm_key, hs.input_shm_key) == 0);
|
||||
assert(back.input_slots == hs.input_slots);
|
||||
assert(back.output_slots == hs.output_slots);
|
||||
assert(back.slot_data_bytes == hs.slot_data_bytes);
|
||||
assert(back.input_slot_data_bytes == hs.input_slot_data_bytes);
|
||||
free(json);
|
||||
|
||||
// Exact wire text: same field names and compact shape the Qt builder emits.
|
||||
oak_ipc_handshake small;
|
||||
memset(&small, 0, sizeof(small));
|
||||
small.protocol_version = 1;
|
||||
strcpy(small.shm_key, "out");
|
||||
strcpy(small.input_shm_key, "in");
|
||||
small.input_slots = 4;
|
||||
small.output_slots = 6;
|
||||
small.slot_data_bytes = 256;
|
||||
small.input_slot_data_bytes = 128;
|
||||
const int n2 = oakengine_ipc_handshake_to_json(&small, NULL, 0);
|
||||
char *json2 = (char *)malloc(size_t(n2) + 1);
|
||||
assert(json2 != NULL);
|
||||
assert(oakengine_ipc_handshake_to_json(&small, json2, n2 + 1) == n2);
|
||||
assert_json_eq(json2,
|
||||
"{\"input_shm_key\":\"in\",\"input_slot_data_bytes\":128,"
|
||||
"\"input_slots\":4,\"output_slots\":6,\"protocol_version\":1,"
|
||||
"\"shm_key\":\"out\",\"slot_data_bytes\":256,"
|
||||
"\"type\":\"handshake\"}");
|
||||
free(json2);
|
||||
|
||||
assert(oakengine_ipc_handshake_to_json(NULL, NULL, 0) == -1);
|
||||
assert(oakengine_ipc_handshake_parse("not json", &back) == 0);
|
||||
assert(oakengine_ipc_handshake_parse(NULL, &back) == 0);
|
||||
}
|
||||
|
||||
static void test_render_frame(void)
|
||||
{
|
||||
oak_ipc_render_frame rf;
|
||||
memset(&rf, 0, sizeof(rf));
|
||||
rf.ticket_id = (int64_t(1) << 52) + 12345; // exact as a JSON double
|
||||
strcpy(rf.node_uuid, "{abcd-1234}");
|
||||
rf.time_num = (int64_t)48000 * 123456789;
|
||||
rf.time_den = int64_t(1) << 40;
|
||||
rf.width = 1920;
|
||||
rf.height = 1080;
|
||||
rf.format = 3;
|
||||
rf.channel_count = 4;
|
||||
rf.mode = 1;
|
||||
rf.input_slot = 2;
|
||||
rf.input_slots[0] = 2;
|
||||
rf.input_slots[1] = 3;
|
||||
rf.input_slot_count = 2;
|
||||
rf.has_color_transform = 1;
|
||||
rf.color_is_display = 1;
|
||||
strcpy(rf.color_output, "sRGB - Display");
|
||||
strcpy(rf.color_view, "ACES 1.0 SDR-video");
|
||||
strcpy(rf.color_look, "None");
|
||||
|
||||
const int needed = oakengine_ipc_render_frame_to_json(&rf, NULL, 0);
|
||||
char *json = (char *)malloc(size_t(needed) + 1);
|
||||
assert(json != NULL);
|
||||
assert(oakengine_ipc_render_frame_to_json(&rf, json, needed + 1) == needed);
|
||||
assert(oakengine_ipc_message_type(json) == OAK_IPC_MSGTYPE_RENDER_FRAME);
|
||||
|
||||
oak_ipc_render_frame back;
|
||||
assert(oakengine_ipc_render_frame_parse(json, &back) == 1);
|
||||
assert(back.ticket_id == rf.ticket_id);
|
||||
assert(strcmp(back.node_uuid, rf.node_uuid) == 0);
|
||||
assert(back.time_num == rf.time_num);
|
||||
assert(back.time_den == rf.time_den);
|
||||
assert(back.width == rf.width && back.height == rf.height);
|
||||
assert(back.format == rf.format && back.channel_count == rf.channel_count);
|
||||
assert(back.mode == rf.mode);
|
||||
assert(back.input_slot == rf.input_slot);
|
||||
assert(back.input_slot_count == 2);
|
||||
assert(back.input_slots[0] == 2 && back.input_slots[1] == 3);
|
||||
assert(back.has_color_transform == 1 && back.color_is_display == 1);
|
||||
assert(strcmp(back.color_output, rf.color_output) == 0);
|
||||
assert(strcmp(back.color_view, rf.color_view) == 0);
|
||||
assert(strcmp(back.color_look, rf.color_look) == 0);
|
||||
free(json);
|
||||
|
||||
// Without a color transform the color keys are omitted from the wire text.
|
||||
oak_ipc_render_frame plain;
|
||||
memset(&plain, 0, sizeof(plain));
|
||||
plain.ticket_id = 99;
|
||||
strcpy(plain.node_uuid, "{abcd-1234}");
|
||||
plain.time_num = 1001;
|
||||
plain.time_den = 30000;
|
||||
plain.width = 1920;
|
||||
plain.height = 1080;
|
||||
plain.format = 3;
|
||||
plain.channel_count = 4;
|
||||
plain.mode = 1;
|
||||
plain.input_slot = 2;
|
||||
plain.input_slots[0] = 2;
|
||||
plain.input_slots[1] = 3;
|
||||
plain.input_slot_count = 2;
|
||||
const int n2 = oakengine_ipc_render_frame_to_json(&plain, NULL, 0);
|
||||
char *json2 = (char *)malloc(size_t(n2) + 1);
|
||||
assert(json2 != NULL);
|
||||
assert(oakengine_ipc_render_frame_to_json(&plain, json2, n2 + 1) == n2);
|
||||
assert(strstr(json2, "has_color_transform") == NULL);
|
||||
assert_json_eq(json2,
|
||||
"{\"channels\":4,\"format\":3,\"height\":1080,"
|
||||
"\"input_slot\":2,\"input_slots\":[2,3],\"mode\":1,"
|
||||
"\"node\":\"{abcd-1234}\",\"ticket\":99,\"time_den\":30000,"
|
||||
"\"time_num\":1001,\"type\":\"render_frame\",\"width\":1920}");
|
||||
free(json2);
|
||||
|
||||
// Defaults for a sparse message, mirroring the Qt from_json defaults.
|
||||
oak_ipc_render_frame sparse;
|
||||
assert(oakengine_ipc_render_frame_parse("{\"type\":\"render_frame\"}",
|
||||
&sparse) == 1);
|
||||
assert(sparse.ticket_id == 0);
|
||||
assert(sparse.time_num == 0 && sparse.time_den == 1);
|
||||
assert(sparse.width == 0 && sparse.height == 0);
|
||||
assert(sparse.format == -1 && sparse.channel_count == 0 && sparse.mode == 0);
|
||||
assert(sparse.input_slot == -1 && sparse.input_slot_count == 0);
|
||||
assert(sparse.has_color_transform == 0);
|
||||
|
||||
// Legacy scalar input_slot folds into the array when it is absent.
|
||||
oak_ipc_render_frame legacy;
|
||||
assert(oakengine_ipc_render_frame_parse(
|
||||
"{\"type\":\"render_frame\",\"ticket\":5,\"input_slot\":3}",
|
||||
&legacy) == 1);
|
||||
assert(legacy.input_slot == 3);
|
||||
assert(legacy.input_slot_count == 1 && legacy.input_slots[0] == 3);
|
||||
|
||||
// Type mismatches are rejected.
|
||||
oak_ipc_handshake hs;
|
||||
memset(&hs, 0, sizeof(hs));
|
||||
const int hn = oakengine_ipc_handshake_to_json(&hs, NULL, 0);
|
||||
char *hjson = (char *)malloc(size_t(hn) + 1);
|
||||
assert(hjson != NULL);
|
||||
assert(oakengine_ipc_handshake_to_json(&hs, hjson, hn + 1) == hn);
|
||||
assert(oakengine_ipc_render_frame_parse(hjson, &back) == 0);
|
||||
assert(oakengine_ipc_handshake_parse(hjson, &hs) == 1);
|
||||
free(hjson);
|
||||
}
|
||||
|
||||
static void test_small_messages(void)
|
||||
{
|
||||
// frame_ready
|
||||
oak_ipc_frame_ready fr;
|
||||
fr.ticket_id = 99;
|
||||
fr.output_slot = 2;
|
||||
const int fn = oakengine_ipc_frame_ready_to_json(&fr, NULL, 0);
|
||||
char *fjson = (char *)malloc(size_t(fn) + 1);
|
||||
assert(fjson != NULL);
|
||||
assert(oakengine_ipc_frame_ready_to_json(&fr, fjson, fn + 1) == fn);
|
||||
assert_json_eq(fjson, "{\"slot\":2,\"ticket\":99,\"type\":\"frame_ready\"}");
|
||||
assert(oakengine_ipc_message_type(fjson) == OAK_IPC_MSGTYPE_FRAME_READY);
|
||||
oak_ipc_frame_ready fr_back;
|
||||
assert(oakengine_ipc_frame_ready_parse(fjson, &fr_back) == 1);
|
||||
assert(fr_back.ticket_id == 99 && fr_back.output_slot == 2);
|
||||
free(fjson);
|
||||
|
||||
// cancel
|
||||
oak_ipc_cancel cancel;
|
||||
cancel.ticket_id = 7;
|
||||
const int cn = oakengine_ipc_cancel_to_json(&cancel, NULL, 0);
|
||||
char *cjson = (char *)malloc(size_t(cn) + 1);
|
||||
assert(cjson != NULL);
|
||||
assert(oakengine_ipc_cancel_to_json(&cancel, cjson, cn + 1) == cn);
|
||||
assert_json_eq(cjson, "{\"ticket\":7,\"type\":\"cancel\"}");
|
||||
assert(oakengine_ipc_message_type(cjson) == OAK_IPC_MSGTYPE_CANCEL);
|
||||
oak_ipc_cancel cancel_back;
|
||||
assert(oakengine_ipc_cancel_parse(cjson, &cancel_back) == 1);
|
||||
assert(cancel_back.ticket_id == 7);
|
||||
// cancel rejects a frame_ready payload.
|
||||
assert(oakengine_ipc_cancel_parse("{\"slot\":2,\"ticket\":7,\"type\":"
|
||||
"\"frame_ready\"}",
|
||||
&cancel_back) == 0);
|
||||
free(cjson);
|
||||
|
||||
// load_graph
|
||||
oak_ipc_load_graph load;
|
||||
strcpy(load.path, "/tmp/oak-render-graph-abc123.ove");
|
||||
const int ln = oakengine_ipc_load_graph_to_json(&load, NULL, 0);
|
||||
char *ljson = (char *)malloc(size_t(ln) + 1);
|
||||
assert(ljson != NULL);
|
||||
assert(oakengine_ipc_load_graph_to_json(&load, ljson, ln + 1) == ln);
|
||||
assert_json_eq(ljson,
|
||||
"{\"path\":\"/tmp/oak-render-graph-abc123.ove\",\"type\":"
|
||||
"\"load_graph\"}");
|
||||
assert(oakengine_ipc_message_type(ljson) == OAK_IPC_MSGTYPE_LOAD_GRAPH);
|
||||
oak_ipc_load_graph load_back;
|
||||
assert(oakengine_ipc_load_graph_parse(ljson, &load_back) == 1);
|
||||
assert(strcmp(load_back.path, load.path) == 0);
|
||||
free(ljson);
|
||||
|
||||
// shutdown
|
||||
const int sn = oakengine_ipc_shutdown_to_json(NULL, 0);
|
||||
char *sjson = (char *)malloc(size_t(sn) + 1);
|
||||
assert(sjson != NULL);
|
||||
assert(oakengine_ipc_shutdown_to_json(sjson, sn + 1) == sn);
|
||||
assert_json_eq(sjson, "{\"type\":\"shutdown\"}");
|
||||
assert(oakengine_ipc_shutdown_parse(sjson) == 1);
|
||||
assert(oakengine_ipc_shutdown_parse("{\"type\":\"cancel\"}") == 0);
|
||||
assert(oakengine_ipc_message_type(sjson) == OAK_IPC_MSGTYPE_SHUTDOWN);
|
||||
free(sjson);
|
||||
|
||||
// error
|
||||
const int en = oakengine_ipc_error_to_json("boom", NULL, 0);
|
||||
char *ejson = (char *)malloc(size_t(en) + 1);
|
||||
assert(ejson != NULL);
|
||||
assert(oakengine_ipc_error_to_json("boom", ejson, en + 1) == en);
|
||||
assert_json_eq(ejson, "{\"message\":\"boom\",\"type\":\"error\"}");
|
||||
assert(oakengine_ipc_message_type(ejson) == OAK_IPC_MSGTYPE_ERROR);
|
||||
char msg[OAK_IPC_ERROR_MESSAGE_CAP];
|
||||
assert(oakengine_ipc_error_parse(ejson, msg, sizeof(msg)) == 1);
|
||||
assert(strcmp(msg, "boom") == 0);
|
||||
assert(oakengine_ipc_error_parse("{\"type\":\"shutdown\"}", msg,
|
||||
sizeof(msg)) == 0);
|
||||
free(ejson);
|
||||
|
||||
// Unknown and malformed input.
|
||||
assert(oakengine_ipc_message_type("{\"type\":\"nope\"}") ==
|
||||
OAK_IPC_MSGTYPE_UNKNOWN);
|
||||
assert(oakengine_ipc_message_type("garbage") == OAK_IPC_MSGTYPE_UNKNOWN);
|
||||
assert(oakengine_ipc_message_type(NULL) == OAK_IPC_MSGTYPE_UNKNOWN);
|
||||
assert(OAK_IPC_MSGTYPE_GRAPH_UPDATE != OAK_IPC_MSGTYPE_UNKNOWN);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
test_shm();
|
||||
test_framepool();
|
||||
test_framepool_over_shm();
|
||||
test_handshake();
|
||||
test_render_frame();
|
||||
test_small_messages();
|
||||
|
||||
printf("oakengine_ipc_test: all assertions passed\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user