Files
GridFire/src/include/gridfire/trigger/trigger_logical.h

515 lines
17 KiB
C++

#pragma once
#include "gridfire/trigger/trigger_abstract.h"
#include "gridfire/trigger/trigger_result.h"
#include <string>
#include <vector>
#include <memory>
#include <stdexcept>
namespace gridfire::trigger {
/**
* @file trigger_logical.h
* @brief Combinators for composing triggers with boolean logic (AND/OR/NOT/EveryNth).
*
* These templates wrap any Trigger<Context> and provide convenient composition. They also
* maintain simple hit/miss counters and implement short-circuit logic in check() and why().
*/
template <typename TriggerContextStruct>
class LogicalTrigger : public Trigger<TriggerContextStruct> {};
/**
* @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 <typename TriggerContextStruct>
class AndTrigger final : public LogicalTrigger<TriggerContextStruct> {
public:
/**
* @brief Construct AND from two triggers (ownership transferred).
*/
AndTrigger(std::unique_ptr<Trigger<TriggerContextStruct>> A, std::unique_ptr<Trigger<TriggerContextStruct>> 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<Trigger<TriggerContextStruct>> m_A;
std::unique_ptr<Trigger<TriggerContextStruct>> 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 <typename TriggerContextStruct>
class OrTrigger final : public LogicalTrigger<TriggerContextStruct> {
public:
OrTrigger(std::unique_ptr<Trigger<TriggerContextStruct>> A, std::unique_ptr<Trigger<TriggerContextStruct>> 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<Trigger<TriggerContextStruct>> m_A;
std::unique_ptr<Trigger<TriggerContextStruct>> 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 <typename TriggerContextStruct>
class NotTrigger final : public LogicalTrigger<TriggerContextStruct> {
public:
explicit NotTrigger(std::unique_ptr<Trigger<TriggerContextStruct>> 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<Trigger<TriggerContextStruct>> 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 <typename TriggerContextStruct>
class EveryNthTrigger final : public LogicalTrigger<TriggerContextStruct> {
public:
explicit EveryNthTrigger(std::unique_ptr<Trigger<TriggerContextStruct>> 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<Trigger<TriggerContextStruct>> 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<typename TriggerContextStruct>
AndTrigger<TriggerContextStruct>::AndTrigger(
std::unique_ptr<Trigger<TriggerContextStruct>> A,
std::unique_ptr<Trigger<TriggerContextStruct>> B
) : m_A(std::move(A)), m_B(std::move(B)) {}
template<typename TriggerContextStruct>
bool AndTrigger<TriggerContextStruct>::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 <typename TriggerContextStruct>
void AndTrigger<TriggerContextStruct>::update(const TriggerContextStruct &ctx) {
m_A->update(ctx);
m_B->update(ctx);
m_updates++;
}
template <typename TriggerContextStruct>
void AndTrigger<TriggerContextStruct>::reset() {
m_A->reset();
m_B->reset();
m_resets++;
m_hits = 0;
m_misses = 0;
m_updates = 0;
}
template<typename TriggerContextStruct>
std::string AndTrigger<TriggerContextStruct>::name() const {
return "AND Trigger";
}
template<typename TriggerContextStruct>
TriggerResult AndTrigger<TriggerContextStruct>::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 <typename TriggerContextStruct>
std::string AndTrigger<TriggerContextStruct>::describe() const {
return "(" + m_A->describe() + ") AND (" + m_B->describe() + ")";
}
template <typename TriggerContextStruct>
size_t AndTrigger<TriggerContextStruct>::numTriggers() const {
return m_hits;
}
template <typename TriggerContextStruct>
size_t AndTrigger<TriggerContextStruct>::numMisses() const {
return m_misses;
}
template <typename TriggerContextStruct>
OrTrigger<TriggerContextStruct>::OrTrigger(
std::unique_ptr<Trigger<TriggerContextStruct>> A,
std::unique_ptr<Trigger<TriggerContextStruct>> B
) : m_A(std::move(A)), m_B(std::move(B)) {}
template <typename TriggerContextStruct>
bool OrTrigger<TriggerContextStruct>::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 <typename TriggerContextStruct>
void OrTrigger<TriggerContextStruct>::update(const TriggerContextStruct &ctx) {
m_A->update(ctx);
m_B->update(ctx);
m_updates++;
}
template <typename TriggerContextStruct>
void OrTrigger<TriggerContextStruct>::reset() {
m_A->reset();
m_B->reset();
m_resets++;
m_hits = 0;
m_misses = 0;
m_updates = 0;
}
template<typename TriggerContextStruct>
std::string OrTrigger<TriggerContextStruct>::name() const {
return "OR Trigger";
}
template<typename TriggerContextStruct>
TriggerResult OrTrigger<TriggerContextStruct>::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 <typename TriggerContextStruct>
std::string OrTrigger<TriggerContextStruct>::describe() const {
return "(" + m_A->describe() + ") OR (" + m_B->describe() + ")";
}
template <typename TriggerContextStruct>
size_t OrTrigger<TriggerContextStruct>::numTriggers() const {
return m_hits;
}
template <typename TriggerContextStruct>
size_t OrTrigger<TriggerContextStruct>::numMisses() const {
return m_misses;
}
template <typename TriggerContextStruct>
NotTrigger<TriggerContextStruct>::NotTrigger(
std::unique_ptr<Trigger<TriggerContextStruct>> A
) : m_A(std::move(A)) {}
template <typename TriggerContextStruct>
bool NotTrigger<TriggerContextStruct>::check(const TriggerContextStruct &ctx) const {
const bool valid = !m_A->check(ctx);
if (valid) {
m_hits++;
} else {
m_misses++;
}
return valid;
}
template <typename TriggerContextStruct>
void NotTrigger<TriggerContextStruct>::update(const TriggerContextStruct &ctx) {
m_A->update(ctx);
m_updates++;
}
template <typename TriggerContextStruct>
void NotTrigger<TriggerContextStruct>::reset() {
m_A->reset();
m_resets++;
m_hits = 0;
m_misses = 0;
m_updates = 0;
}
template<typename TriggerContextStruct>
std::string NotTrigger<TriggerContextStruct>::name() const {
return "NOT Trigger";
}
template<typename TriggerContextStruct>
TriggerResult NotTrigger<TriggerContextStruct>::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 <typename TriggerContextStruct>
std::string NotTrigger<TriggerContextStruct>::describe() const {
return "NOT (" + m_A->describe() + ")";
}
template <typename TriggerContextStruct>
size_t NotTrigger<TriggerContextStruct>::numTriggers() const {
return m_hits;
}
template <typename TriggerContextStruct>
size_t NotTrigger<TriggerContextStruct>::numMisses() const {
return m_misses;
}
template <typename TriggerContextStruct>
EveryNthTrigger<TriggerContextStruct>::EveryNthTrigger(std::unique_ptr<Trigger<TriggerContextStruct>> 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 <typename TriggerContextStruct>
bool EveryNthTrigger<TriggerContextStruct>::check(const TriggerContextStruct &ctx) const
{
if (m_A->check(ctx) && (m_counter % m_N == 0)) {
m_hits++;
return true;
}
m_misses++;
return false;
}
template <typename TriggerContextStruct>
void EveryNthTrigger<TriggerContextStruct>::update(const TriggerContextStruct &ctx) {
if (m_A->check(ctx)) {
m_counter++;
}
m_A->update(ctx);
m_updates++;
}
template <typename TriggerContextStruct>
void EveryNthTrigger<TriggerContextStruct>::reset() {
m_A->reset();
m_resets++;
m_counter = 0;
m_hits = 0;
m_misses = 0;
m_updates = 0;
}
template <typename TriggerContextStruct>
std::string EveryNthTrigger<TriggerContextStruct>::name() const {
return "Every Nth Trigger";
}
template <typename TriggerContextStruct>
TriggerResult EveryNthTrigger<TriggerContextStruct>::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 <typename TriggerContextStruct>
std::string EveryNthTrigger<TriggerContextStruct>::describe() const {
return "Every " + std::to_string(m_N) + "th (" + m_A->describe() + ")";
}
template <typename TriggerContextStruct>
size_t EveryNthTrigger<TriggerContextStruct>::numTriggers() const {
return m_hits;
}
template <typename TriggerContextStruct>
size_t EveryNthTrigger<TriggerContextStruct>::numMisses() const {
return m_misses;
}
}