/* * SPDX-License-Identifier: AGPL-3.0-or-later * Copyright (C) 2025 Sergej Görzen * This file is part of OmiLAXR. */ using System; using System.Collections.Generic; using System.Linq; namespace OmiLAXR.Composers.HigherComposers { /// /// Abstract base class for higher-level composers that aggregate and process multiple related statements. /// Higher composers can analyze patterns of statements to detect complex events or behaviors. /// /// The type of statement this composer processes. public abstract class HigherComposer : PipelineComponent, IComposer where T : IStatement { private string _name; /// /// Gets the name of this composer. /// /// The name of the composer. public virtual string GetName() => _name; public abstract string GetDataStandardVersion(); public ComposerGroup GetGroup() => ComposerGroup.Other; /// /// Defines the conditions that this composer looks for in statements. /// Must be implemented by derived classes to specify matching criteria. /// /// Dictionary mapping condition names to their match conditions. protected abstract Dictionary> Conditions(); /// /// Helper method to find a pipeline of the specified type. /// /// The type of pipeline to find. /// The first pipeline of the specified type found in the scene. protected static TP GetPipeline() where TP : Pipeline => FindObject(true); /// /// Structure that defines a condition for matching statements. /// Contains the condition logic, expected number of matches, and collection of matching statements. /// /// The type of statement this condition applies to. protected readonly struct MatchCondition where T0 : IStatement { /// /// Delegate type for the condition function that evaluates statements. /// /// The statement to evaluate. /// True if the statement matches the condition, false otherwise. public delegate bool MatchConditionHandler(T0 statement); /// /// The condition function that determines if a statement matches. /// public readonly MatchConditionHandler Condition; /// /// The minimum number of matching statements required to satisfy this condition. /// public readonly int ExpectedMatches; /// /// Collection of statements that have matched this condition. /// public readonly List MatchingStatements; /// /// Gets the current number of statements that match this condition. /// public int ActualMatches => MatchingStatements.Count; /// /// Determines if the condition has collected enough matching statements. /// public bool HasEnoughMatches => ActualMatches >= ExpectedMatches; /// /// Initializes a new instance of the MatchCondition struct. /// /// The minimum number of matching statements required. /// The function that evaluates if statements match. public MatchCondition(int expectedMatches, MatchConditionHandler condition) { Condition = condition; ExpectedMatches = expectedMatches; MatchingStatements = new List(); } /// /// Evaluates a statement against the condition and adds it to matching statements if it matches. /// /// The statement to evaluate. public void CheckCondition(T0 statement) { if (Condition(statement)) MatchingStatements.Add(statement); } } /// /// Event that fires when this composer creates a new statement. /// public event ComposerAction AfterComposed; /// /// Indicates that this is a higher-level composer. /// public bool IsHigherComposer => true; /// /// Indicates whether this composer is currently enabled. /// public bool IsEnabled => enabled; /// /// Gets information about the author of this composer. /// /// Author information. public abstract Author GetAuthor(); /// /// Examines a statement to see if it matches any of the conditions defined by this composer. /// If enough statements match either any or all conditions, triggers the appropriate handler. /// /// The statement to examine. public void LookFor(IStatement statement) { // Skip if statement is not of the expected type if (statement.GetType() != typeof(T)) return; var conditions = Conditions(); var stmt = (T)statement; // Check statement against all conditions foreach (var condition in conditions.Values) { condition.CheckCondition(stmt); } // Check if any conditions have enough matches var matchesAny = conditions.Where(c => c.Value.HasEnoughMatches) .ToDictionary>, string, IEnumerable>( c => c.Key, c => c.Value.MatchingStatements ); // If any conditions have enough matches, call the any-match handler if (matchesAny.Count > 0) { OnMatchAnyConditions(matchesAny); } // Check if all conditions have enough matches var matchesAll = conditions.Values.All(c => c.HasEnoughMatches); if (!matchesAll) return; // If all conditions have enough matches, call the all-match handler var matchingStatements = conditions.ToDictionary( i => i.Key, i => (IEnumerable)i.Value.MatchingStatements ); OnMatchAllConditions(matchingStatements); } /// /// Called when all conditions have collected enough matching statements. /// Must be implemented by derived classes to handle the matched statements. /// /// Dictionary mapping condition names to their matching statements. protected abstract void OnMatchAllConditions(Dictionary> matchingStatements); /// /// Called when any conditions have collected enough matching statements. /// Can be overridden by derived classes to handle partial matches. /// /// Dictionary mapping condition names to their matching statements. protected virtual void OnMatchAnyConditions(Dictionary> matchingStatements) { // Default implementation does nothing - derived classes can override } /// /// Sends a new statement through the pipeline. /// /// The statement to send. /// Whether to process the statement immediately or queue it. [Obsolete("Use SendStatement(IStatement) instead. Immediate is not needed anymore due efficient thread queue handling.", true)] protected void SendStatement(IStatement statement, bool immediate) => SendStatement(statement); protected void SendStatement(IStatement statement) { if (!IsEnabled) return; AfterComposed?.Invoke(this, statement); } /// /// Convenience method to send a statement for immediate processing. /// /// The statement to send immediately. [Obsolete("Use SendStatement(ITrackingBehaviour, IStatement) instead. Immediate is not needed anymore due efficient thread queue handling.", true)] protected void SendStatementImmediate(IStatement statement) => SendStatement(statement, immediate: true); } }