#include #include #include #include #include #include #include #include "node.h" #include "utils/logger.h" #include "utils/shared_state.h" namespace rk3588 { namespace { struct ConfigSnapshot { std::string id; std::string state_key; std::set class_ids; // empty => any bool require_confirmed = true; int min_count = 1; int64_t max_age_ms = 800; // state freshness int64_t min_interval_ms = 300; // throttle output float min_box_area_ratio = 0.0f; // optional filter bool pass_through_if_no_state = false; }; static bool ParseIntSet(const SimpleJson& arr, std::set& out, std::string& err) { if (!arr.IsArray()) { err = "expected array"; return false; } out.clear(); for (const auto& it : arr.AsArray()) { const int v = it.AsInt(-1); if (v < 0) continue; out.insert(v); } return true; } static bool BuildConfigSnapshot(const SimpleJson& config, std::shared_ptr& out, std::string& err) { auto snap = std::make_shared(); snap->id = config.ValueOr("id", "gate"); snap->state_key = config.ValueOr("state_key", ""); snap->require_confirmed = config.ValueOr("require_confirmed", true); snap->min_count = config.ValueOr("min_count", 1); snap->max_age_ms = static_cast(config.ValueOr("max_age_ms", 800)); snap->min_interval_ms = static_cast(config.ValueOr("min_interval_ms", 300)); snap->min_box_area_ratio = config.ValueOr("min_box_area_ratio", 0.0f); snap->pass_through_if_no_state = config.ValueOr("pass_through_if_no_state", false); if (const SimpleJson* ids = config.Find("class_ids")) { if (!ParseIntSet(*ids, snap->class_ids, err)) { err = "class_ids: " + err; return false; } } if (snap->min_count < 1) snap->min_count = 1; if (snap->max_age_ms < 0) snap->max_age_ms = 0; if (snap->min_interval_ms < 0) snap->min_interval_ms = 0; if (snap->min_box_area_ratio < 0.0f) snap->min_box_area_ratio = 0.0f; if (snap->min_box_area_ratio > 1.0f) snap->min_box_area_ratio = 1.0f; out = std::move(snap); return true; } } // namespace class GateNode final : public INode { public: std::string Id() const override { return id_; } std::string Type() const override { return "gate"; } bool Init(const SimpleJson& config, const NodeContext& ctx) override { std::string err; std::shared_ptr snap; if (!BuildConfigSnapshot(config, snap, err)) { LogError("[GateNode] invalid config: " + err); return false; } id_ = snap->id; ctx_ = ctx; { std::lock_guard lk(mu_); cfg_ = std::move(snap); } return true; } bool Start() override { return true; } void Stop() override {} bool UpdateConfig(const SimpleJson& new_config) override { std::string err; std::shared_ptr snap; if (!BuildConfigSnapshot(new_config, snap, err)) { LogWarn("[GateNode] UpdateConfig ignored: " + err); return false; } if (snap->id != id_) { LogWarn("[GateNode] UpdateConfig ignored: id mismatch"); return false; } std::lock_guard lk(mu_); cfg_ = std::move(snap); return true; } NodeStatus Process(FramePtr frame) override { if (!frame) return NodeStatus::DROP; std::shared_ptr cfg; { std::lock_guard lk(mu_); cfg = cfg_; } if (!cfg) return NodeStatus::DROP; if (cfg->state_key.empty()) { // Without a state source, this node cannot decide; choose conservative behavior. if (cfg->pass_through_if_no_state) { PushToDownstream(std::move(frame)); return NodeStatus::OK; } return NodeStatus::DROP; } const uint64_t now_us = NowSteadyUs(); auto snap = SharedState::Instance().GetTargets(cfg->state_key); if (!snap) { if (cfg->pass_through_if_no_state) { PushToDownstream(std::move(frame)); return NodeStatus::OK; } return NodeStatus::DROP; } if (cfg->max_age_ms > 0) { const uint64_t age_us = (now_us > snap->update_steady_us) ? (now_us - snap->update_steady_us) : 0; if (age_us > static_cast(cfg->max_age_ms) * 1000ULL) { return NodeStatus::DROP; } } const double img_area = (snap->img_w > 0 && snap->img_h > 0) ? (static_cast(snap->img_w) * static_cast(snap->img_h)) : 0.0; int match_count = 0; for (const auto& obj : snap->objects) { if (!cfg->class_ids.empty() && cfg->class_ids.count(obj.cls_id) == 0) continue; if (cfg->require_confirmed && !obj.confirmed) continue; if (cfg->min_box_area_ratio > 0.0f && img_area > 0.0) { const double a = static_cast(obj.bbox.w) * static_cast(obj.bbox.h); const double r = a / img_area; if (r < static_cast(cfg->min_box_area_ratio)) continue; } ++match_count; if (match_count >= cfg->min_count) break; } if (match_count < cfg->min_count) { return NodeStatus::DROP; } if (cfg->min_interval_ms > 0) { const uint64_t last = last_emit_us_.load(std::memory_order_relaxed); if (last != 0 && now_us < last + static_cast(cfg->min_interval_ms) * 1000ULL) { return NodeStatus::DROP; } last_emit_us_.store(now_us, std::memory_order_relaxed); } PushToDownstream(std::move(frame)); return NodeStatus::OK; } private: void PushToDownstream(FramePtr frame) { for (auto& q : ctx_.output_queues) { if (!q) continue; q->Push(frame); } } std::string id_; NodeContext ctx_{}; mutable std::mutex mu_; std::shared_ptr cfg_; std::atomic last_emit_us_{0}; }; REGISTER_NODE(GateNode, "gate"); } // namespace rk3588