#pragma once #include "gridfire/trigger/trigger_abstract.h" #include "gridfire/trigger/trigger_result.h" #include #include #include #include namespace gridfire::trigger { /** * @file trigger_logical.h * @brief Combinators for composing triggers with boolean logic (AND/OR/NOT/EveryNth). * * These templates wrap any Trigger and provide convenient composition. They also * maintain simple hit/miss counters and implement short-circuit logic in check() and why(). */ template class LogicalTrigger : public Trigger {}; /** * @class AndTrigger * @brief Logical conjunction of two triggers with short-circuit evaluation. * * check(ctx) returns A.check(ctx) && B.check(ctx). The why(ctx) explanation short-circuits * if A is false, avoiding evaluation of B. update(ctx) calls update() on both A and B. * * Counters (mutable) are incremented inside const check(): m_hits on true; m_misses on false; * m_updates on each update(); m_resets on reset(). * * @par Example * @code * auto t = AndTrigger(ctxA, ctxB); * if (t.check(ctx)) { (void)ctx; } * @endcode */ template class AndTrigger final : public LogicalTrigger { public: /** * @brief Construct AND from two triggers (ownership transferred). */ AndTrigger(std::unique_ptr> A, std::unique_ptr> B); ~AndTrigger() override = default; /** * @brief Evaluate A && B; increments hit/miss counters. */ bool check(const TriggerContextStruct& ctx) const override; /** * @brief Update both sub-triggers and increment update counter. */ void update(const TriggerContextStruct& ctx) override; /** * @brief Reset both sub-triggers and local counters. */ void reset() override; /** * @brief Human-readable name. */ std::string name() const override; /** * @brief Structured explanation; short-circuits on A=false. */ TriggerResult why(const TriggerContextStruct& ctx) const override; /** * @brief Description expression e.g. "(A) AND (B)". */ std::string describe() const override; /** * @brief Number of true evaluations since last reset. */ size_t numTriggers() const override; /** * @brief Number of false evaluations since last reset. */ size_t numMisses() const override; private: std::unique_ptr> m_A; std::unique_ptr> m_B; mutable size_t m_hits = 0; mutable size_t m_misses = 0; mutable size_t m_updates = 0; mutable size_t m_resets = 0; }; /** * @class OrTrigger * @brief Logical disjunction of two triggers with short-circuit evaluation. * * check(ctx) returns A.check(ctx) || B.check(ctx). why(ctx) returns early when A is true. * update(ctx) calls update() on both A and B. Counters behave as in AndTrigger. */ template class OrTrigger final : public LogicalTrigger { public: OrTrigger(std::unique_ptr> A, std::unique_ptr> B); ~OrTrigger() override = default; bool check(const TriggerContextStruct& ctx) const override; void update(const TriggerContextStruct& ctx) override; void reset() override; std::string name() const override; TriggerResult why(const TriggerContextStruct& ctx) const override; std::string describe() const override; size_t numTriggers() const override; size_t numMisses() const override; private: std::unique_ptr> m_A; std::unique_ptr> m_B; mutable size_t m_hits = 0; mutable size_t m_misses = 0; mutable size_t m_updates = 0; mutable size_t m_resets = 0; }; /** * @class NotTrigger * @brief Logical negation of a trigger. * * check(ctx) returns !A.check(ctx). why(ctx) explains the inverted condition. Counter * semantics match the other logical triggers. */ template class NotTrigger final : public LogicalTrigger { public: explicit NotTrigger(std::unique_ptr> A); ~NotTrigger() override = default; bool check(const TriggerContextStruct& ctx) const override; void update(const TriggerContextStruct& ctx) override; void reset() override; std::string name() const override; TriggerResult why(const TriggerContextStruct& ctx) const override; std::string describe() const override; size_t numTriggers() const override; size_t numMisses() const override; private: std::unique_ptr> m_A; mutable size_t m_hits = 0; mutable size_t m_misses = 0; mutable size_t m_updates = 0; mutable size_t m_resets = 0; }; /** * @class EveryNthTrigger * @brief Pass-through trigger that fires every Nth time its child trigger is true. * * On update(ctx), increments an internal counter when A.check(ctx) is true. check(ctx) * returns true only when A.check(ctx) is true and the internal counter is a multiple of N. * * @throws std::invalid_argument When constructed with N==0. */ template class EveryNthTrigger final : public LogicalTrigger { public: explicit EveryNthTrigger(std::unique_ptr> A, size_t N); ~EveryNthTrigger() override = default; bool check(const TriggerContextStruct& ctx) const override; void update(const TriggerContextStruct& ctx) override; void reset() override; std::string name() const override; TriggerResult why(const TriggerContextStruct& ctx) const override; std::string describe() const override; size_t numTriggers() const override; size_t numMisses() const override; private: std::unique_ptr> m_A; size_t m_N; mutable size_t m_counter = 0; mutable size_t m_hits = 0; mutable size_t m_misses = 0; mutable size_t m_updates = 0; mutable size_t m_resets = 0; }; /////////////////////////////// // Templated Implementations // /////////////////////////////// template AndTrigger::AndTrigger( std::unique_ptr> A, std::unique_ptr> B ) : m_A(std::move(A)), m_B(std::move(B)) {} template bool AndTrigger::check(const TriggerContextStruct &ctx) const { const bool valid = m_A->check(ctx) && m_B->check(ctx); if (valid) { m_hits++; } else { m_misses++; } return valid; } template void AndTrigger::update(const TriggerContextStruct &ctx) { m_A->update(ctx); m_B->update(ctx); m_updates++; } template void AndTrigger::reset() { m_A->reset(); m_B->reset(); m_resets++; m_hits = 0; m_misses = 0; m_updates = 0; } template std::string AndTrigger::name() const { return "AND Trigger"; } template TriggerResult AndTrigger::why(const TriggerContextStruct &ctx) const { TriggerResult result; result.name = name(); TriggerResult A_result = m_A->why(ctx); result.causes.push_back(A_result); if (!A_result.value) { // Short Circuit result.value = false; result.description = "Failed because A (" + A_result.name + ") is false."; return result; } TriggerResult B_result = m_B->why(ctx); result.causes.push_back(B_result); if (!B_result.value) { result.value = false; result.description = "Failed because B (" + B_result.name + ") is false."; return result; } result.value = true; result.description = "Succeeded because both A (" + A_result.name + ") and B (" + B_result.description + ") are true."; return result; } template std::string AndTrigger::describe() const { return "(" + m_A->describe() + ") AND (" + m_B->describe() + ")"; } template size_t AndTrigger::numTriggers() const { return m_hits; } template size_t AndTrigger::numMisses() const { return m_misses; } template OrTrigger::OrTrigger( std::unique_ptr> A, std::unique_ptr> B ) : m_A(std::move(A)), m_B(std::move(B)) {} template bool OrTrigger::check(const TriggerContextStruct &ctx) const { const bool valid = m_A->check(ctx) || m_B->check(ctx); if (valid) { m_hits++; } else { m_misses++; } return valid; } template void OrTrigger::update(const TriggerContextStruct &ctx) { m_A->update(ctx); m_B->update(ctx); m_updates++; } template void OrTrigger::reset() { m_A->reset(); m_B->reset(); m_resets++; m_hits = 0; m_misses = 0; m_updates = 0; } template std::string OrTrigger::name() const { return "OR Trigger"; } template TriggerResult OrTrigger::why(const TriggerContextStruct &ctx) const { TriggerResult result; result.name = name(); TriggerResult A_result = m_A->why(ctx); result.causes.push_back(A_result); if (A_result.value) { // Short Circuit result.value = true; result.description = "Succeeded because A (" + A_result.name + ") is true."; return result; } TriggerResult B_result = m_B->why(ctx); result.causes.push_back(B_result); if (B_result.value) { result.value = true; result.description = "Succeeded because B (" + B_result.name + ") is true."; return result; } result.value = false; result.description = "Failed because both A (" + A_result.name + ") and B (" + B_result.name + ") are false."; return result; } template std::string OrTrigger::describe() const { return "(" + m_A->describe() + ") OR (" + m_B->describe() + ")"; } template size_t OrTrigger::numTriggers() const { return m_hits; } template size_t OrTrigger::numMisses() const { return m_misses; } template NotTrigger::NotTrigger( std::unique_ptr> A ) : m_A(std::move(A)) {} template bool NotTrigger::check(const TriggerContextStruct &ctx) const { const bool valid = !m_A->check(ctx); if (valid) { m_hits++; } else { m_misses++; } return valid; } template void NotTrigger::update(const TriggerContextStruct &ctx) { m_A->update(ctx); m_updates++; } template void NotTrigger::reset() { m_A->reset(); m_resets++; m_hits = 0; m_misses = 0; m_updates = 0; } template std::string NotTrigger::name() const { return "NOT Trigger"; } template TriggerResult NotTrigger::why(const TriggerContextStruct &ctx) const { TriggerResult result; result.name = name(); TriggerResult A_result = m_A->why(ctx); result.causes.push_back(A_result); if (A_result.value) { result.value = false; result.description = "Failed because A (" + A_result.name + ") is true."; return result; } result.value = true; result.description = "Succeeded because A (" + A_result.name + ") is false."; return result; } template std::string NotTrigger::describe() const { return "NOT (" + m_A->describe() + ")"; } template size_t NotTrigger::numTriggers() const { return m_hits; } template size_t NotTrigger::numMisses() const { return m_misses; } template EveryNthTrigger::EveryNthTrigger(std::unique_ptr> A, const size_t N) : m_A(std::move(A)), m_N(N) { if (N == 0) { throw std::invalid_argument("N must be greater than 0."); } } template bool EveryNthTrigger::check(const TriggerContextStruct &ctx) const { if (m_A->check(ctx) && (m_counter % m_N == 0)) { m_hits++; return true; } m_misses++; return false; } template void EveryNthTrigger::update(const TriggerContextStruct &ctx) { if (m_A->check(ctx)) { m_counter++; } m_A->update(ctx); m_updates++; } template void EveryNthTrigger::reset() { m_A->reset(); m_resets++; m_counter = 0; m_hits = 0; m_misses = 0; m_updates = 0; } template std::string EveryNthTrigger::name() const { return "Every Nth Trigger"; } template TriggerResult EveryNthTrigger::why(const TriggerContextStruct &ctx) const { TriggerResult result; result.name = name(); TriggerResult A_result = m_A->why(ctx); result.causes.push_back(A_result); if (!A_result.value) { result.value = false; result.description = "Failed because A (" + A_result.name + ") is false."; return result; } if (m_counter % m_N == 0) { result.value = true; result.description = "Succeeded because A (" + A_result.name + ") is true and the counter (" + std::to_string(m_counter) + ") is a multiple of N (" + std::to_string(m_N) + ")."; return result; } result.value = false; result.description = "Failed because the counter (" + std::to_string(m_counter) + ") is not a multiple of N (" + std::to_string(m_N) + ")."; return result; } template std::string EveryNthTrigger::describe() const { return "Every " + std::to_string(m_N) + "th (" + m_A->describe() + ")"; } template size_t EveryNthTrigger::numTriggers() const { return m_hits; } template size_t EveryNthTrigger::numMisses() const { return m_misses; } }