Files
cuframes-composer/src/cpp/layouts_c_api.cpp
T
gx 6e0273f4b4 Phase 11b F: extern "C" ABI shim + production deploy
В прод деплоен gx/cuframes-composer:0.11b-step1 — C++ ядро
работает через ABI shim, старые C-callers (grid_record.c, control.c,
frigate_mqtt.c) использует те же cfc_composer_* функции.

Что в этом коммите:
  - src/cpp/composer_c_api.cpp: extern "C" обёртки над cfc::Composer
    методами. Полный набор: _create/_destroy/_compose/_add_overlay/
    _find_overlay/_set_layout/_current_layout/_add_pool_source/
    _set_motion_mode/_get_motion_mode/_motion_pulse/_get_health.
  - src/cpp/layouts_c_api.cpp: extern "C" обёртки над template_loader
    для cfc_layout_find/_all/_load_file/_reload/_loaded_path/_to_pixels.
  - cpp/template_loader: global registry (current_templates / set_*
    / load_into_current) — единый источник истины. Composer и C ABI
    shim читают один и тот же mutex-защищённый vector<LayoutTemplate>.
    Hot-reload через ZMQ cfc_layout_load_file подхватывается composer'ом
    на следующем кадре без рестарта.
  - cpp/composer: pick_best_fit, set_layout, maybe_relayout читают
    current_templates() вместо локального snapshot.
  - cpp/composer: backward-compat overlay list (add_overlay/find_overlay)
    + manual cells support (для C API без motion-mode).
  - cpp/composer compose_frame: после Layout.render() рендерит overlays
    (CLI text/icon/border + Frigate detbox) поверх.
  - Удалены: src/composer.c (заменён composer_c_api.cpp + composer.cpp),
    src/layouts.c (заменён layouts_c_api.cpp + template_loader.cpp).
  - Оставлено как есть: src/overlay.c (PNG/text/border/detbox CLI overlays
    — реализация не меняется, доступ через cfc_overlay_*).
  - src/CMakeLists.txt: COMPOSER_SOURCES_C минус composer.c, layouts.c,
    COMPOSER_SOURCES_CPP плюс composer_c_api.cpp, layouts_c_api.cpp.

Production smoke (R9-88.23):
  [cfc/loader] /opt/templates.json: loaded 7 templates
  [cfc/composer] templates loaded: 7 (path='/opt/templates.json')
  [cfc/composer] pool+ cam-parking prio=100 / cam-gate_lpr prio=90 / ...
  [cfc/composer] motion_mode=1 ttl=45000ms pool=4
  [cfc/composer] grow → template='tpl_1' active=1
PASS.

Refs: #195 (Phase 11b C++ refactor).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 08:57:30 +01:00

137 lines
4.2 KiB
C++

/* layouts_c_api — extern "C" ABI shim над template_loader (Phase 11b).
*
* Сохраняет совместимость с control.c::cmd_list_layouts/_get_layout/_set_layout
* через старый интерфейс cfc_layout_find / cfc_layout_all.
*
* Стратегия: static cache из cfc_layout_t structs, заполняется при
* load_file/reload. cfc_layout_find/_all возвращают указатели в этот cache.
*
* Лицензия: LGPL-2.1+
*/
#include "../../include/cuframes_composer/layouts.h"
#include "../../include/cuframes_composer/cpp/template.hpp"
#include "../../include/cuframes_composer/cpp/template_loader.hpp"
#include <cstdio>
#include <cstring>
#include <mutex>
#include <string>
#include <vector>
namespace {
static std::mutex g_mu;
static std::vector<cfc_layout_t> g_c_cache;
static std::string g_loaded_path;
static std::vector<cfc::LayoutTemplate> g_cpp_cache;
void rebuild_c_cache_locked()
{
g_c_cache.clear();
g_c_cache.reserve(g_cpp_cache.size());
for (const auto& t : g_cpp_cache) {
cfc_layout_t l{};
std::strncpy(l.name, t.name.c_str(), sizeof(l.name) - 1);
l.priority = t.priority;
l.nb_cells = static_cast<int>(t.cells.size());
if (l.nb_cells > CFC_LAYOUT_MAX_CELLS) l.nb_cells = CFC_LAYOUT_MAX_CELLS;
l.nb_camera_cells = 0;
for (int i = 0; i < l.nb_cells; i++) {
const auto& c = t.cells[i];
l.cells[i].col = c.col;
l.cells[i].row = c.row;
l.cells[i].cs = c.cs;
l.cells[i].rs = c.rs;
l.cells[i].role = (c.role == cfc::CellRole::Widget)
? CFC_CELL_WIDGET : CFC_CELL_CAMERA;
l.cells[i].order = c.order;
std::strncpy(l.cells[i].widget, c.widget.c_str(),
sizeof(l.cells[i].widget) - 1);
if (l.cells[i].role == CFC_CELL_CAMERA) l.nb_camera_cells++;
}
g_c_cache.push_back(l);
}
}
void ensure_loaded_locked()
{
/* Источник истины = global registry; кеш C-структур пересинхронизируется
* каждый раз когда состав изменился (поэтому простая проверка empty
* не годится — может появиться обновление через load_file). */
g_cpp_cache = cfc::current_templates();
rebuild_c_cache_locked();
}
} // namespace
extern "C" {
const cfc_layout_t* cfc_layout_find(const char* name)
{
if (!name) return nullptr;
std::lock_guard<std::mutex> lk(g_mu);
ensure_loaded_locked();
for (const auto& l : g_c_cache) {
if (std::strcmp(l.name, name) == 0) return &l;
}
return nullptr;
}
const cfc_layout_t* cfc_layout_all(int* out_count)
{
std::lock_guard<std::mutex> lk(g_mu);
ensure_loaded_locked();
if (out_count) *out_count = static_cast<int>(g_c_cache.size());
return g_c_cache.data();
}
void cfc_layout_to_pixels(const cfc_cell_t* cell, int W, int H,
int* out_x, int* out_y, int* out_w, int* out_h)
{
if (!cell) return;
int x = (cell->col * W) / CFC_GRID_COLS;
int y = (cell->row * H) / CFC_GRID_ROWS;
int w = (cell->cs * W) / CFC_GRID_COLS;
int h = (cell->rs * H) / CFC_GRID_ROWS;
x &= ~1; y &= ~1; w &= ~1; h &= ~1;
if (x + w > W) w = W - x;
if (y + h > H) h = H - y;
if (out_x) *out_x = x;
if (out_y) *out_y = y;
if (out_w) *out_w = w;
if (out_h) *out_h = h;
}
int cfc_layout_load_file(const char* path)
{
if (!path) return -3;
int r = cfc::load_into_current(path); /* обновит global registry */
if (r > 0) {
std::lock_guard<std::mutex> lk(g_mu);
g_cpp_cache = cfc::current_templates();
rebuild_c_cache_locked();
g_loaded_path = path;
}
return r;
}
int cfc_layout_reload(void)
{
std::string path;
{
std::lock_guard<std::mutex> lk(g_mu);
path = g_loaded_path;
}
if (path.empty()) return -1;
return cfc_layout_load_file(path.c_str());
}
const char* cfc_layout_loaded_path(void)
{
std::lock_guard<std::mutex> lk(g_mu);
return g_loaded_path.empty() ? nullptr : g_loaded_path.c_str();
}
} // extern "C"