/* * 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; using OmiLAXR.TrackingBehaviours; using UnityEngine; using Object = UnityEngine.Object; namespace OmiLAXR.Composers { /// /// Abstract base class for statement composers that process tracking behaviors. /// Manages statement caching, composition, and delivery to endpoints. /// /// Type of tracking behavior this composer handles [DefaultExecutionOrder(-100)] public abstract class Composer : DataProviderPipelineComponent, IComposer where T : PipelineComponent, ITrackingBehaviour { /// /// Array of tracking behaviors this composer is processing /// [HideInInspector] public T[] trackingBehaviours; /// /// Cache for storing statements with string keys /// private readonly Dictionary _statementCache = new Dictionary(); /// /// Cache for storing statements with integer keys /// private readonly Dictionary _statementCacheInt = new Dictionary(); /// /// Stores a statement in cache with string key /// public void StoreStatement(string key, IStatement statement) => _statementCache[key] = statement; /// /// Stores a statement in cache with integer key /// public void StoreStatement(int key, IStatement statement) => _statementCacheInt[key] = statement; /// /// Retrieves cached statement by string key, returns null if not found /// public IStatement RestoreStatement(string key) => _statementCache.TryGetValue(key, out var statement) ? statement : null; /// /// Retrieves cached statement by integer key, returns null if not found /// public IStatement RestoreStatement(int key) => _statementCacheInt.TryGetValue(key, out var statement) ? statement : null; /// /// Initializes the composer, finds tracking behaviors, and starts composition /// protected override void OnEnable() { base.OnEnable(); // Generate composer name from class name _name = GetType().Name.Replace("Composer", ""); // Find all tracking behaviors of the specified type trackingBehaviours = GetTrackingBehaviours(); // Start composing statements for each tracking behavior foreach (var trackingBehaviour in trackingBehaviours) Compose(trackingBehaviour); // Process any queued statements HandleWaitList(); } /// /// Gets author information for statements created by this composer /// public abstract Author GetAuthor(); /// /// Cached composer name derived from class name /// private string _name; /// /// Returns the display name of this composer /// public virtual string GetName() => _name; /// /// Returns the logical grouping for this composer /// public virtual ComposerGroup GetGroup() => ComposerGroup.Other; /// /// Indicates if this is a higher-level composer that processes other composers' output /// public virtual bool IsHigherComposer => false; /// /// Event fired after a statement has been composed and is ready for delivery /// public event ComposerAction AfterComposed; /// /// Queue for statements waiting for event handlers to be registered /// private readonly List _waitList = new List(); /// /// Finds all tracking behaviors of specified type in the scene /// /// Whether to include inactive GameObjects protected static TB[] GetTrackingBehaviours(bool includeInactive = false) where TB : Object, ITrackingBehaviour => FindObjects(includeInactive); /// /// Obsolete: Use SendStatement(ITrackingBehaviour, IStatement) instead /// [Obsolete( "Use SendStatement(ITrackingBehaviour, IStatement) instead. Immediate is not needed anymore due efficient thread queue handling.", true)] protected void SendStatement(ITrackingBehaviour statementOwner, IStatement statement, bool immediate) => SendStatement(statementOwner, statement); /// /// Sends a composed statement for delivery to endpoints /// /// The tracking behavior that generated this statement /// The composed statement to send protected void SendStatement(ITrackingBehaviour statementOwner, IStatement statement) { // Set ownership and composer information statement.SetOwner(statementOwner); statement.SetComposer(this); // If no handlers registered, queue statement for later if (AfterComposed?.GetInvocationList().Length < 1) { print("Enqueued statement: " + statement.ToShortString() + " in waitlist."); _waitList.Add(statement); return; } // Send statement to registered handlers AfterComposed?.Invoke(this, statement); } /// /// Obsolete: Use SendStatement(ITrackingBehaviour, IStatement) instead /// [Obsolete( "Use SendStatement(ITrackingBehaviour, IStatement) instead. Immediate is not needed anymore due efficient thread queue handling.", true)] protected void SendStatementImmediate(ITrackingBehaviour statementOwner, IStatement statement) => SendStatement(statementOwner, statement, immediate: true); /// /// Obsolete: Use SendStatement(ITrackingBehaviour, IStatement, bool) instead /// [Obsolete("Use SendStatement(ITrackingBehaviour, IStatement, bool) instead.")] protected void SendStatement(IStatement statement, bool immediate = false) { SendStatement(trackingBehaviours.First(), statement, immediate); } /// /// Obsolete: Use SendStatementImmediate(ITrackingBehaviour, IStatement) instead /// [Obsolete("Use SendStatementImmediate(ITrackingBehaviour, IStatement) instead.")] protected void SendStatementImmediate(IStatement statement) => SendStatement(statement, immediate: true); /// /// Implements composition logic for the specific tracking behavior type. /// Override this method to define how statements are created from tracking events. /// /// The tracking behavior to compose statements for protected abstract void Compose(T tb); /// /// Processes queued statements when event handlers become available /// private void HandleWaitList() { // Send all queued statements if handlers are now available if (_waitList.Count > 0 && AfterComposed?.GetInvocationList().Length > 0) { foreach (var statement in _waitList) { AfterComposed?.Invoke(this, statement); } _waitList.Clear(); } } /// /// Checks for queued statements each frame and processes them when handlers become available /// private void Update() { HandleWaitList(); } } }