beb8e1baa0
Что в этом коммите:
Decoration реализации:
- cpp/label_decoration.hpp/.cpp — FreeType atlas + cugrid_blit_rgba_nv12.
UTF-8 декодер, atlas в VRAM (RAII через CudaBuffer), rebuild при set_text.
- cpp/border_decoration.hpp/.cpp — 4 cugrid_fill_nv12 (top/bottom/left/right).
Cell реализации:
- cpp/camera_cell.hpp/.cpp — cfc_source_get_latest + cugrid_resize_nv12.
Non-owning указатель на cfc_source_t (pool владеет).
- cpp/widget_cell.hpp/.cpp — тёмный fill placeholder.
- cpp/blank_cell.hpp/.cpp — BT.709 black fill.
Layout и Template:
- cpp/template.hpp — LayoutTemplate { name, cells[], priority }.
8×8 микро-сетка (kGridCols=kGridRows=8). to_pixels() переводит в Rect.
- cpp/layout.hpp/.cpp — vector<unique_ptr<Cell>>, apply() создаёт
CameraCell/WidgetCell/BlankCell + Decorations (Label с "{key} prio={N}").
- cpp/template_loader.hpp/.cpp — JSON → vector<LayoutTemplate> через json-c.
builtin_templates() = { tpl_1, tpl_4 } как fallback.
SourcePool:
- cpp/source_pool.hpp/.cpp — owner cfc_source_t*, motion state атомарный,
zone-filter в motion_pulse. Pool entries — non-copyable unique_ptr.
Composer:
- cpp/composer.hpp/.cpp — owner SourcePool + templates + Layout + output.
Алгоритмы: pick_best_fit (min nb_camera_cells >= need + priority tie-break),
collect_active (drawable AND motion_within_TTL), asymmetric hysteresis
(рост сразу через std::includes, сжатие — wait shrink_hysteresis_ms).
Public C++ API: set_motion_mode / set_layout / load_templates / compose_frame.
ООП-гипотеза smoke:
- examples/grid_record_cpp.cpp — минимальный smoke без NVENC. Init composer,
compose_frame N раз, dump NV12 в файл. Проверяет что C++ модель
компилируется, линкуется с C-кодом (source.c, nvenc.c остались на C через
extern "C"), и реально рисует кадр.
Производительность сохранена:
- Один output буфер VMM, передаётся как NV12Ref (read-write reference) во все
cells/decorations — НИКАКИХ memcpy на cells boundary.
- Virtual call overhead: 1 indirect call per cell per frame. Negligible.
- Heap allocations только при apply_template (раз в N секунд при relayout).
Build:
- CMakeLists.txt: CXX language, C++17.
- src/CMakeLists.txt: COMPOSER_SOURCES_CPP добавлен в lib.
- examples/CMakeLists.txt: grid_record_cpp.
Smoke test run jammy:
[cfc/loader] docker/templates.json: loaded 7 templates
[smoke] composer 1920x1080 templates=7 sources=0 motion=0
[smoke] wrote 3317760 bytes (Y=2211840 UV=1105920) to /out/blank.nv12
Build PASS, init PASS, compose PASS, dump PASS.
Что НЕ сделано:
- extern "C" ABI shim для control.c / grid_record.c (старый C-композитор
всё ещё единственный для prod stack).
- Удаление старых composer.c / overlay.c / layouts.c.
- Live deploy в прод (Step 1-3 функциональность).
- JSON ZMQ hot-reload (был в Step 3 C-version, восстановить в C++).
Refs: #195 (Phase 11b C++ refactor).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
96 lines
3.0 KiB
C++
96 lines
3.0 KiB
C++
/* SourcePool — пул cuframes-источников композитора (Phase 11b).
|
||
*
|
||
* Каждая запись: cuframes_key + frigate_camera + priority + cfc_source_t* +
|
||
* motion state (last_motion_ms, zone-filter). Pool создаётся при старте
|
||
* композитора (через add() вызовы) и живёт всю сессию.
|
||
*
|
||
* Cells (CameraCell) держат non-owning указатели на cfc_source_t — pool
|
||
* владеет.
|
||
*
|
||
* Лицензия: LGPL-2.1+
|
||
*/
|
||
|
||
#ifndef CUFRAMES_COMPOSER_CPP_SOURCE_POOL_HPP
|
||
#define CUFRAMES_COMPOSER_CPP_SOURCE_POOL_HPP
|
||
|
||
#include "../source.h"
|
||
|
||
#include <atomic>
|
||
#include <memory>
|
||
#include <mutex>
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
namespace cfc {
|
||
|
||
struct PoolEntry {
|
||
std::string cuframes_key;
|
||
std::string frigate_camera;
|
||
int priority = 0;
|
||
cfc_source_t* source = nullptr;
|
||
std::atomic<std::int64_t> last_motion_ms{0};
|
||
std::vector<std::string> required_zones;
|
||
|
||
/* Получить snapshot для drawable-checks без локов. */
|
||
cfc_source_state_t state() const {
|
||
if (!source) return CFC_SOURCE_DISCONNECTED;
|
||
cfc_source_snapshot_t s{};
|
||
cfc_source_get_latest(source, &s);
|
||
return s.state;
|
||
}
|
||
|
||
bool drawable() const {
|
||
cfc_source_state_t st = state();
|
||
return st == CFC_SOURCE_ACTIVE || st == CFC_SOURCE_STALE;
|
||
}
|
||
};
|
||
|
||
class SourcePool {
|
||
public:
|
||
SourcePool() = default;
|
||
~SourcePool();
|
||
|
||
SourcePool(const SourcePool&) = delete;
|
||
SourcePool& operator=(const SourcePool&) = delete;
|
||
|
||
/* Параметры подписки cuframes (default per cfc_source_config_t). */
|
||
struct SubscribeOpts {
|
||
int cuda_device = 0;
|
||
std::string consumer_prefix = "composer";
|
||
int reconnect_min_ms = 1000;
|
||
int reconnect_max_ms = 30000;
|
||
int stale_threshold_ms = 500;
|
||
int dead_threshold_ms = 5000;
|
||
};
|
||
|
||
/* Добавить источник в pool. Возвращает индекс или -1. */
|
||
int add(const std::string& cuframes_key,
|
||
const std::string& frigate_camera,
|
||
int priority,
|
||
const std::vector<std::string>& zones,
|
||
const SubscribeOpts& opts);
|
||
|
||
int size() const { return static_cast<int>(entries_.size()); }
|
||
PoolEntry* by_index(int i) { return i >= 0 && i < size() ? entries_[i].get() : nullptr; }
|
||
PoolEntry* by_key(const std::string& key);
|
||
|
||
/* Уведомить о motion (вызывается из Frigate MQTT subscriber'а через
|
||
* C-shim). Если zone-filter задан — проверяет пересечение. */
|
||
void motion_pulse(const std::string& frigate_camera,
|
||
const std::vector<std::string>& current_zones);
|
||
|
||
/* Итерация (для best-fit selection и health). */
|
||
template <typename F>
|
||
void for_each(F&& fn) {
|
||
for (auto& e : entries_) fn(*e);
|
||
}
|
||
|
||
private:
|
||
std::vector<std::unique_ptr<PoolEntry>> entries_;
|
||
std::mutex mu_;
|
||
};
|
||
|
||
} // namespace cfc
|
||
|
||
#endif /* CUFRAMES_COMPOSER_CPP_SOURCE_POOL_HPP */
|