refactor(codec): de-Qt oakcodec and wrap it in a pure C ABI; switch common handles to refcounted value structs
- oakcodec: de-Qt all 20 sources (QThread decode loop -> std::thread,
QObject/signals -> callbacks), pure C ABI in include/codec with
refcounted neutral handles (OakFrame/OakDecoder/OakEncoder),
framemanager moved in from render, frame_to_buffer/buffer_to_frame
moved in from oakcommon oiioutils, codec->task via submit callback
(M8 will register), all cross-module calls go through the other
side's C API, -fvisibility=hidden + OAKCODEC_API
- oakcommon: handles become refcounted value structs
{ctx, addref, release, abi_version} (FFmpeg-style), pass-by-value
signatures, free() as release wrapper; init_from_native/get_native
for copyable value objects; OakCommonXxx renamed to OakXxx
- oakcommon: add logging (log_debug/info/warning/critical with level
filtering and sink injection) + printf-style oakcommon_log C wrapper
- oakrender: add CancelAtom C API family; complete
oakrender_color_processor_convert_frame; fix get_processor() missing
definition and OCIO env var lookup
- tests: oakcommon 174, oaknode 96, oakrender 42, oakcodec 18, all
green in their standalone builds
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
target_sources(oakrender PRIVATE
|
||||
renderer.cpp
|
||||
cache.cpp
|
||||
cancelatom.cpp
|
||||
color.cpp
|
||||
manager.cpp
|
||||
)
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "../../../include/render/cancelatom.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
|
||||
#include "alivecount.h"
|
||||
|
||||
#include "cancelatom.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Heap box behind every OakCancelAtom's ctx pointer.
|
||||
*
|
||||
* Holds the wrapped CancelAtom plus its atomic reference count. addref
|
||||
* and release are emitted in this translation unit so the function
|
||||
* pointers stored in a handle always run code from the DLL that created
|
||||
* the object.
|
||||
*/
|
||||
struct CancelAtomBox {
|
||||
olive::CancelAtom impl;
|
||||
std::atomic<uint32_t> refs;
|
||||
|
||||
CancelAtomBox()
|
||||
: refs(1)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
CancelAtomBox *box(OakCancelAtom atom)
|
||||
{
|
||||
return static_cast<CancelAtomBox *>(atom.ctx);
|
||||
}
|
||||
|
||||
olive::CancelAtom *impl(OakCancelAtom atom)
|
||||
{
|
||||
auto *b = box(atom);
|
||||
return b ? &b->impl : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle addref thunk: atomically increments the count.
|
||||
*/
|
||||
void cancel_atom_addref(void *ctx)
|
||||
{
|
||||
auto *b = static_cast<CancelAtomBox *>(ctx);
|
||||
if (b)
|
||||
b->refs.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle release thunk: decrements the count, destroys at zero.
|
||||
*/
|
||||
void cancel_atom_release(void *ctx)
|
||||
{
|
||||
auto *b = static_cast<CancelAtomBox *>(ctx);
|
||||
if (b && b->refs.fetch_sub(1, std::memory_order_acq_rel) == 1) {
|
||||
delete b;
|
||||
oakrender_c_api::alive_dec();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OakCancelAtom oakrender_cancelatom_init(void)
|
||||
{
|
||||
OakCancelAtom h = {};
|
||||
try {
|
||||
h.ctx = new CancelAtomBox();
|
||||
} catch (...) {
|
||||
h.ctx = nullptr;
|
||||
}
|
||||
h.addref = &cancel_atom_addref;
|
||||
h.release = &cancel_atom_release;
|
||||
h.abi_version = OAKRENDER_ABI_VERSION;
|
||||
if (h.ctx)
|
||||
oakrender_c_api::alive_inc();
|
||||
return h;
|
||||
}
|
||||
|
||||
void oakrender_cancelatom_free(OakCancelAtom *atom)
|
||||
{
|
||||
if (!atom || !atom->ctx || !atom->release)
|
||||
return;
|
||||
atom->release(atom->ctx);
|
||||
atom->ctx = nullptr;
|
||||
}
|
||||
|
||||
int oakrender_cancelatom_cancel(OakCancelAtom atom)
|
||||
{
|
||||
auto *c = impl(atom);
|
||||
if (!c)
|
||||
return OAKRENDER_E_INVALID;
|
||||
c->cancel();
|
||||
return OAKRENDER_OK;
|
||||
}
|
||||
|
||||
int oakrender_cancelatom_is_cancelled(OakCancelAtom atom, int *cancelled)
|
||||
{
|
||||
auto *c = impl(atom);
|
||||
if (!c || !cancelled)
|
||||
return OAKRENDER_E_INVALID;
|
||||
*cancelled = c->is_cancelled() ? 1 : 0;
|
||||
return OAKRENDER_OK;
|
||||
}
|
||||
|
||||
int oakrender_cancelatom_heard_cancel(OakCancelAtom atom, int *heard)
|
||||
{
|
||||
auto *c = impl(atom);
|
||||
if (!c || !heard)
|
||||
return OAKRENDER_E_INVALID;
|
||||
*heard = c->heard_cancel() ? 1 : 0;
|
||||
return OAKRENDER_OK;
|
||||
}
|
||||
+30
-10
@@ -30,6 +30,7 @@
|
||||
|
||||
#include "color/colormanager/colormanager.h"
|
||||
#include "filefunctions.h"
|
||||
#include <OpenColorIO/OpenColorIO.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -57,7 +58,7 @@ OakColorProcessor *oakrender_color_processor_create(const char *src_space,
|
||||
return nullptr;
|
||||
}
|
||||
try {
|
||||
ocio::ConstConfigRcPtr config = olive::ColorManager::get_default_config();
|
||||
OCIO_NAMESPACE::ConstConfigRcPtr config = olive::ColorManager::get_default_config();
|
||||
if (!config) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -69,17 +70,17 @@ OakColorProcessor *oakrender_color_processor_create(const char *src_space,
|
||||
src = config->getCanonicalName(src_space);
|
||||
}
|
||||
|
||||
// OCIO failures are non-fatal (matching the C++ behavior): the
|
||||
// OCIO_NAMESPACE failures are non-fatal (matching the C++ behavior): the
|
||||
// handle is still returned, but holds a null processor and
|
||||
// conversions pass through.
|
||||
ocio::ConstProcessorRcPtr processor;
|
||||
OCIO_NAMESPACE::ConstProcessorRcPtr processor;
|
||||
try {
|
||||
if (direction == OAKRENDER_COLOR_DIRECTION_NORMAL) {
|
||||
processor = config->getProcessor(src.c_str(), dst_transform);
|
||||
} else {
|
||||
processor = config->getProcessor(dst_transform, src.c_str());
|
||||
}
|
||||
} catch (ocio::Exception &) {
|
||||
} catch (OCIO_NAMESPACE::Exception &) {
|
||||
processor = nullptr;
|
||||
}
|
||||
|
||||
@@ -169,7 +170,7 @@ int oakrender_color_manager_display_transform(const char *display,
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
try {
|
||||
ocio::ConstConfigRcPtr config = olive::ColorManager::get_default_config();
|
||||
OCIO_NAMESPACE::ConstConfigRcPtr config = olive::ColorManager::get_default_config();
|
||||
if (!config) {
|
||||
return OAKRENDER_E_STATE;
|
||||
}
|
||||
@@ -197,25 +198,44 @@ int oakrender_color_manager_display_transform(const char *display,
|
||||
}
|
||||
|
||||
// Source = the config's reference colorspace (role lookup).
|
||||
ocio::ConstColorSpaceRcPtr ref_cs =
|
||||
config->getColorSpace(ocio::ROLE_REFERENCE);
|
||||
OCIO_NAMESPACE::ConstColorSpaceRcPtr ref_cs =
|
||||
config->getColorSpace(OCIO_NAMESPACE::ROLE_REFERENCE);
|
||||
if (!ref_cs) {
|
||||
return OAKRENDER_E_STATE;
|
||||
}
|
||||
|
||||
auto dvt = ocio::DisplayViewTransform::Create();
|
||||
auto dvt = OCIO_NAMESPACE::DisplayViewTransform::Create();
|
||||
dvt->setSrc(ref_cs->getName());
|
||||
dvt->setDisplay(display);
|
||||
dvt->setView(view);
|
||||
|
||||
ocio::ConstProcessorRcPtr processor = config->getProcessor(dvt);
|
||||
OCIO_NAMESPACE::ConstProcessorRcPtr processor = config->getProcessor(dvt);
|
||||
if (!processor) {
|
||||
return OAKRENDER_E_NOT_FOUND;
|
||||
}
|
||||
return write_string(processor->getCacheID(), buf, n);
|
||||
} catch (ocio::Exception &) {
|
||||
} catch (OCIO_NAMESPACE::Exception &) {
|
||||
return OAKRENDER_E_NOT_FOUND;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakrender_color_processor_convert_frame(OakColorProcessor *processor,
|
||||
OakCodecFrame *frame)
|
||||
{
|
||||
if (!processor || !processor->ptr || !frame || !frame->ptr) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
try {
|
||||
// In-place: ColorProcessor::convert_frame() applies the CPU
|
||||
// processor to the frame's pixel buffer through an
|
||||
// OCIO::PackedImageDesc view. A processor whose underlying OCIO
|
||||
// processor is null (creation failure was non-fatal) is a
|
||||
// pass-through and still reports success, mirroring the C++ API.
|
||||
processor->ptr->convert_frame(frame->ptr);
|
||||
return OAKRENDER_OK;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,9 @@ add_library(oakrender SHARED ${OAKRENDER_SOURCES})
|
||||
|
||||
# Dynamically loaded render backends (oak_renderer_* C ABI). Loaded via
|
||||
# dlopen by DynamicRenderer from the app render_backends/ dir.
|
||||
add_library(oakgl SHARED opengl/openglbackend_c.cpp)
|
||||
add_library(oakgl2 SHARED opengl/openglbackend_c.cpp)
|
||||
add_library(oakvulkan SHARED vulkan/vulkanbackend_c.cpp)
|
||||
foreach(backend oakgl oakvulkan)
|
||||
foreach(backend oakgl2 oakvulkan)
|
||||
target_link_libraries(${backend} PRIVATE oakrender)
|
||||
endforeach()
|
||||
|
||||
|
||||
@@ -134,6 +134,11 @@ void ColorProcessor::convert_frame(Frame *f)
|
||||
cpu_processor_->apply(img);
|
||||
}
|
||||
|
||||
ocio::ConstProcessorRcPtr ColorProcessor::get_processor()
|
||||
{
|
||||
return processor_;
|
||||
}
|
||||
|
||||
Color ColorProcessor::convert_color(const Color &in)
|
||||
{
|
||||
if (!cpu_processor_) {
|
||||
@@ -163,10 +168,6 @@ ColorProcessorPtr ColorProcessor::create(ocio::ConstProcessorRcPtr processor)
|
||||
return std::make_shared<ColorProcessor>(processor);
|
||||
}
|
||||
|
||||
ocio::ConstProcessorRcPtr ColorProcessor::get_processor()
|
||||
{
|
||||
return processor_;
|
||||
}
|
||||
|
||||
void ColorProcessor::convert_frame(FramePtr f)
|
||||
{
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "framemanager.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
FrameManager *FrameManager::instance_ = nullptr;
|
||||
const int FrameManager::k_frame_lifetime = 5000;
|
||||
|
||||
static int64_t current_msecs_since_epoch()
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
}
|
||||
|
||||
void FrameManager::create_instance()
|
||||
{
|
||||
instance_ = new FrameManager();
|
||||
}
|
||||
|
||||
void FrameManager::destroy_instance()
|
||||
{
|
||||
delete instance_;
|
||||
instance_ = nullptr;
|
||||
}
|
||||
|
||||
FrameManager *FrameManager::instance()
|
||||
{
|
||||
return instance_;
|
||||
}
|
||||
|
||||
char *FrameManager::allocate(int size)
|
||||
{
|
||||
if (instance()) {
|
||||
return instance()->allocate_from_pool(size);
|
||||
} else {
|
||||
return new char[size];
|
||||
}
|
||||
}
|
||||
|
||||
void FrameManager::deallocate(int size, char *buffer)
|
||||
{
|
||||
if (instance()) {
|
||||
instance()->deallocate_to_pool(size, buffer);
|
||||
} else {
|
||||
delete[] buffer;
|
||||
}
|
||||
}
|
||||
|
||||
FrameManager::FrameManager()
|
||||
: gc_thread_stop_(false)
|
||||
{
|
||||
// Replaces the QTimer that fired garbage_collection() every
|
||||
// k_frame_lifetime ms
|
||||
gc_thread_ = std::thread([this]() {
|
||||
while (!gc_thread_stop_.load()) {
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(k_frame_lifetime));
|
||||
if (gc_thread_stop_.load()) {
|
||||
break;
|
||||
}
|
||||
garbage_collection();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
char *FrameManager::allocate_from_pool(int size)
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
|
||||
std::list<Buffer> &buffer_list = pool_[size];
|
||||
char *buf = nullptr;
|
||||
|
||||
if (buffer_list.empty()) {
|
||||
buf = new char[size];
|
||||
} else {
|
||||
// Take this buffer from the list
|
||||
buf = buffer_list.front().data;
|
||||
buffer_list.pop_front();
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
void FrameManager::deallocate_to_pool(int size, char *buffer)
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
|
||||
std::list<Buffer> &buffer_list = pool_[size];
|
||||
|
||||
buffer_list.push_back({ current_msecs_since_epoch(), buffer });
|
||||
}
|
||||
|
||||
void FrameManager::garbage_collection()
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
|
||||
int64_t min_life = current_msecs_since_epoch() - k_frame_lifetime;
|
||||
|
||||
for (auto it = pool_.begin(); it != pool_.end(); it++) {
|
||||
std::list<Buffer> &list = it->second;
|
||||
|
||||
while (list.size() > 0 && list.front().time < min_life) {
|
||||
delete[] list.front().data;
|
||||
list.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FrameManager::~FrameManager()
|
||||
{
|
||||
gc_thread_stop_.store(true);
|
||||
if (gc_thread_.joinable()) {
|
||||
gc_thread_.join();
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
|
||||
for (auto it = pool_.begin(); it != pool_.end(); it++) {
|
||||
std::list<Buffer> &list = it->second;
|
||||
for (auto jt = list.begin(); jt != list.end(); jt++) {
|
||||
delete[](*jt).data;
|
||||
}
|
||||
}
|
||||
|
||||
pool_.clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_FRAMEMANAGER_H
|
||||
#define OAK_FRAMEMANAGER_H
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class FrameManager {
|
||||
public:
|
||||
static void create_instance();
|
||||
|
||||
static void destroy_instance();
|
||||
|
||||
static FrameManager *instance();
|
||||
|
||||
static char *allocate(int size);
|
||||
|
||||
static void deallocate(int size, char *buffer);
|
||||
|
||||
private:
|
||||
FrameManager();
|
||||
|
||||
~FrameManager();
|
||||
|
||||
FrameManager(const FrameManager &) = delete;
|
||||
FrameManager &operator=(const FrameManager &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Allocate buffer
|
||||
*
|
||||
* Caller takes ownership of buffer and can delete it if they want. It can also be returned to
|
||||
* the manager with Deallocate and potentially be re-used later.
|
||||
*
|
||||
* Thread-safe.
|
||||
*/
|
||||
char *allocate_from_pool(int size);
|
||||
|
||||
/**
|
||||
* @brief Deallocate buffer
|
||||
*
|
||||
* Manager will take ownership and buffer will stay allocated for some time in case it can be
|
||||
* re-used.
|
||||
*
|
||||
* Thread-safe.
|
||||
*/
|
||||
void deallocate_to_pool(int size, char *buffer);
|
||||
|
||||
static FrameManager *instance_;
|
||||
|
||||
static const int k_frame_lifetime;
|
||||
|
||||
struct Buffer {
|
||||
int64_t time;
|
||||
char *data;
|
||||
};
|
||||
|
||||
std::map<int, std::list<Buffer>> pool_;
|
||||
|
||||
std::mutex mutex_;
|
||||
|
||||
// QTimer replacement: periodic garbage collection on a background
|
||||
// thread (the timer used to fire in the GUI thread)
|
||||
std::thread gc_thread_;
|
||||
std::atomic<bool> gc_thread_stop_;
|
||||
|
||||
// Formerly a QTimer timeout slot
|
||||
void garbage_collection();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_FRAMEMANAGER_H
|
||||
@@ -116,10 +116,12 @@ endif()
|
||||
# Symbols of the not-yet-split engine modules (codec/audio/task/config/
|
||||
# pluginSupport/...) dangle by design. The backend libraries resolve most
|
||||
# symbols from liboakrender at load time and dangle the same way.
|
||||
foreach(t oakrender oakgl oakvulkan)
|
||||
target_link_options(${t} PRIVATE
|
||||
"-undefined" "dynamic_lookup"
|
||||
)
|
||||
foreach(t oakrender oakgl oakgl2 oakvulkan)
|
||||
if(TARGET ${t})
|
||||
target_link_options(${t} PRIVATE
|
||||
"-undefined" "dynamic_lookup"
|
||||
)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
target_link_libraries(oakrender PRIVATE
|
||||
|
||||
@@ -17,6 +17,7 @@ endif()
|
||||
|
||||
add_executable(oakrender-gtest
|
||||
cache_test.cpp
|
||||
cancelatom_test.cpp
|
||||
color_test.cpp
|
||||
manager_test.cpp
|
||||
renderer_test.cpp
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - 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/>.
|
||||
|
||||
***/
|
||||
|
||||
// Same-dir quoted include would hit the src/node/transition/render/
|
||||
// bridge header (olive::CancelAtom) first on this build's include path;
|
||||
// reference the public header relative to this file instead.
|
||||
#include "../../../include/render/cancelatom.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "render/cache.h" /* oakrender_debug_alive_count */
|
||||
|
||||
TEST(OakCancelAtomTest, InitFree)
|
||||
{
|
||||
const int alive_before = oakrender_debug_alive_count();
|
||||
|
||||
OakCancelAtom atom = oakrender_cancelatom_init();
|
||||
ASSERT_NE(atom.ctx, nullptr);
|
||||
EXPECT_NE(atom.addref, nullptr);
|
||||
EXPECT_NE(atom.release, nullptr);
|
||||
EXPECT_EQ(atom.abi_version, OAKRENDER_ABI_VERSION);
|
||||
EXPECT_EQ(oakrender_debug_alive_count(), alive_before + 1);
|
||||
|
||||
oakrender_cancelatom_free(&atom);
|
||||
EXPECT_EQ(atom.ctx, nullptr);
|
||||
EXPECT_EQ(oakrender_debug_alive_count(), alive_before);
|
||||
|
||||
// NULL and empty handles are no-ops
|
||||
oakrender_cancelatom_free(nullptr);
|
||||
oakrender_cancelatom_free(&atom);
|
||||
EXPECT_EQ(oakrender_debug_alive_count(), alive_before);
|
||||
}
|
||||
|
||||
TEST(OakCancelAtomTest, CancelStateMachine)
|
||||
{
|
||||
OakCancelAtom atom = oakrender_cancelatom_init();
|
||||
ASSERT_NE(atom.ctx, nullptr);
|
||||
|
||||
int flag = -1;
|
||||
EXPECT_EQ(oakrender_cancelatom_is_cancelled(atom, &flag), OAKRENDER_OK);
|
||||
EXPECT_EQ(flag, 0);
|
||||
|
||||
EXPECT_EQ(oakrender_cancelatom_cancel(atom), OAKRENDER_OK);
|
||||
|
||||
// Cancel must not be heard until a consumer reads the flag
|
||||
int heard = -1;
|
||||
EXPECT_EQ(oakrender_cancelatom_heard_cancel(atom, &heard), OAKRENDER_OK);
|
||||
EXPECT_EQ(heard, 0);
|
||||
|
||||
EXPECT_EQ(oakrender_cancelatom_is_cancelled(atom, &flag), OAKRENDER_OK);
|
||||
EXPECT_EQ(flag, 1);
|
||||
EXPECT_EQ(oakrender_cancelatom_heard_cancel(atom, &heard), OAKRENDER_OK);
|
||||
EXPECT_EQ(heard, 1);
|
||||
|
||||
oakrender_cancelatom_free(&atom);
|
||||
}
|
||||
|
||||
TEST(OakCancelAtomTest, InvalidArgs)
|
||||
{
|
||||
OakCancelAtom empty = {};
|
||||
|
||||
int flag = 7;
|
||||
EXPECT_EQ(oakrender_cancelatom_cancel(empty), OAKRENDER_E_INVALID);
|
||||
EXPECT_EQ(oakrender_cancelatom_is_cancelled(empty, &flag),
|
||||
OAKRENDER_E_INVALID);
|
||||
EXPECT_EQ(flag, 7);
|
||||
EXPECT_EQ(oakrender_cancelatom_heard_cancel(empty, &flag),
|
||||
OAKRENDER_E_INVALID);
|
||||
EXPECT_EQ(flag, 7);
|
||||
|
||||
OakCancelAtom atom = oakrender_cancelatom_init();
|
||||
ASSERT_NE(atom.ctx, nullptr);
|
||||
EXPECT_EQ(oakrender_cancelatom_is_cancelled(atom, nullptr),
|
||||
OAKRENDER_E_INVALID);
|
||||
EXPECT_EQ(oakrender_cancelatom_heard_cancel(atom, nullptr),
|
||||
OAKRENDER_E_INVALID);
|
||||
|
||||
oakrender_cancelatom_free(&atom);
|
||||
}
|
||||
|
||||
TEST(OakCancelAtomTest, AddrefReleaseCountSemantics)
|
||||
{
|
||||
const int alive_before = oakrender_debug_alive_count();
|
||||
|
||||
OakCancelAtom atom = oakrender_cancelatom_init();
|
||||
ASSERT_NE(atom.ctx, nullptr);
|
||||
|
||||
// Copy the struct and take an extra reference; both copies share the
|
||||
// same underlying object and cancel state
|
||||
OakCancelAtom copy = atom;
|
||||
copy.addref(copy.ctx);
|
||||
|
||||
EXPECT_EQ(oakrender_cancelatom_cancel(atom), OAKRENDER_OK);
|
||||
int flag = 0;
|
||||
EXPECT_EQ(oakrender_cancelatom_is_cancelled(copy, &flag), OAKRENDER_OK);
|
||||
EXPECT_EQ(flag, 1);
|
||||
|
||||
// Releasing one reference keeps the object alive for the other
|
||||
atom.release(atom.ctx);
|
||||
EXPECT_EQ(oakrender_debug_alive_count(), alive_before + 1);
|
||||
flag = 0;
|
||||
EXPECT_EQ(oakrender_cancelatom_is_cancelled(copy, &flag), OAKRENDER_OK);
|
||||
EXPECT_EQ(flag, 1);
|
||||
|
||||
// The final reference destroys the object
|
||||
oakrender_cancelatom_free(©);
|
||||
EXPECT_EQ(copy.ctx, nullptr);
|
||||
EXPECT_EQ(oakrender_debug_alive_count(), alive_before);
|
||||
}
|
||||
|
||||
TEST(OakCancelAtomTest, AddrefReleaseNullCtxIsSafe)
|
||||
{
|
||||
// NULL ctx must not crash the thunks
|
||||
OakCancelAtom atom = oakrender_cancelatom_init();
|
||||
ASSERT_NE(atom.ctx, nullptr);
|
||||
atom.addref(nullptr);
|
||||
atom.release(nullptr);
|
||||
oakrender_cancelatom_free(&atom);
|
||||
}
|
||||
Reference in New Issue
Block a user