using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using OmiLAXR.Schedules; using UnityEngine; using Object = UnityEngine.Object; namespace OmiLAXR.TrackingBehaviours { /// /// Non-generic base class for tracking behaviors that work with general Unity Objects. /// Provides a simplified API by using Object as the tracking type. /// public abstract class TrackingBehaviour : TrackingBehaviour { /// /// Empty implementation to be overridden by derived classes if needed. /// Called after objects have been filtered through the pipeline. Can be called multiple times. /// /// Array of filtered objects to track protected override void AfterFilteredObjects(Object[] objects) { // Base implementation is empty - derived classes should override if needed } } /// /// Generic base class for all tracking behaviors in the OmiLAXR system. /// Provides functionality to track specific types of objects through the pipeline. /// Executes early in Unity's order to ensure tracking is set up before other components. /// /// Type of objects to track [DefaultExecutionOrder(-1)] public abstract class TrackingBehaviour : ActorPipelineComponent, ITrackingBehaviour where T : Object { /// /// Collection of schedulers that control when this tracking behavior executes. /// protected readonly List Schedulers = new List(); protected IntervalTicker SetInterval(Action onTick, IntervalTicker.Settings settings, Action onTickStart = null, Action onTickEnd = null) { var it = new IntervalTicker(this, settings, onTick, onTickStart, onTickEnd); Schedulers.Add(it); if (settings.runImmediate) it.Start(); return it; } protected IntervalTicker SetInterval(Action onTick, float intervalSeconds = 1.0f) => SetInterval(onTick, new IntervalTicker.Settings() { intervalSeconds = intervalSeconds }); protected TimeoutTicker SetTimeout(Action onTick, TimeoutTicker.Settings settings, Action onTickStart = null, Action onTickEnd = null) { var to = new TimeoutTicker(this, settings, onTick, onTickStart, onTickEnd); Schedulers.Add(to); if (settings.runImmediate) to.Start(); return to; } protected TimeoutTicker SetTimeout(Action onTick, float timeoutSeconds = 1.0f) => SetTimeout(onTick, new TimeoutTicker.Settings() { timeoutSeconds = timeoutSeconds }); /// /// Initializes the tracking behavior by setting up schedulers and event subscriptions. /// Connects to pipeline events to receive objects for tracking. /// protected virtual void OnEnable() { try { Pipeline.BeforeStartedPipeline += _ => { if (!enabled) return; BeforeStartedPipeline(Pipeline); }; // Subscribe to pipeline events Pipeline.AfterStartedPipeline += _ => { if (!enabled) return; OnStartedPipeline(Pipeline); StartSchedules(); }; Pipeline.BeforeStoppedPipeline += _ => { if (!enabled) return; BeforeStoppedPipeline(Pipeline); }; Pipeline.AfterStoppedPipeline += _ => { if (!enabled) return; OnStoppedPipeline(Pipeline); ClearSchedules(); Dispose(AllFilteredObjects.Select(o => o as Object).ToArray()); ; }; // Handle objects found by the pipeline Pipeline.AfterFoundObjects += (objects) => { if (!enabled) return; // Skip Select if not needed (optimization) AfterFoundObjects(typeof(T) == typeof(Object) ? objects as T[] : Select(objects)); }; // Handle objects after they've been filtered by the pipeline Pipeline.AfterFilteredObjects += (objects) => { if (!enabled) return; // Skip Select if not needed (optimization) if (typeof(T) == typeof(Object) || objects.Length == 0) AfterFilteredObjects(objects as T[]); else { var selectedObjects = Select(objects); AllFilteredObjects.AddRange(selectedObjects); AfterFilteredObjects(selectedObjects); } }; } catch (Exception ex) { DebugLog.OmiLAXR.Error($"Error initializing {GetType().Name}: {ex.Message}"); } } protected virtual void BeforeStartedPipeline(Pipeline pipeline) {} protected virtual void BeforeStoppedPipeline(Pipeline pipeline) {} /// /// Called when the pipeline starts. Override to implement custom start behavior. /// /// Reference to the started pipeline protected virtual void OnStartedPipeline(Pipeline pipeline) {} /// /// Called before the pipeline stops. Override to implement custom stop behavior. /// /// Reference to the stopping pipeline protected virtual void OnStoppedPipeline(Pipeline pipeline) {} /// /// Called after objects are found by the pipeline but before filtering. /// Override to implement custom processing on found objects. /// /// Array of found objects of type T protected virtual void AfterFoundObjects(T[] objects) {} /// /// Called after objects have been filtered by the pipeline. /// Must be implemented by derived classes to process the filtered objects. Can be called multiple times. /// /// Array of filtered objects of type T protected abstract void AfterFilteredObjects(T[] objects); /// /// Stores the currently selected objects for tracking. /// protected readonly List AllFilteredObjects = new List(); protected void StartSchedules() { foreach (var scheduler in Schedulers) scheduler.Start(); } protected void StopSchedules() { foreach (var scheduler in Schedulers) scheduler.Stop(); } protected void ClearSchedules() { StopSchedules(); Schedulers.Clear(); } protected virtual void OnDisable() { StopSchedules(); DisposeAllTrackingEvents(); } /// /// Unbinds all tracking events to prevent memory leaks and unexpected behavior. /// protected void DisposeAllTrackingEvents() { // Get all fields of type ITrackingBehaviourEvent var fields = GetTrackingBehaviourEvents(); foreach (var field in fields) { // Get the value of the field from the current instance var fieldValue = field.GetValue(this) as ITrackingBehaviourEvent; // Call UnbindAll if the field is not null fieldValue?.UnbindAll(); } } /// /// Cleans up resources when tracking stops. /// Unbinds all tracking events by default. /// /// Array of objects that were being tracked protected virtual void Dispose(Object[] objects) { DisposeAllTrackingEvents(); } /// /// Retrieves all fields in this class that implement ITrackingBehaviourEvent. /// Used for automatic event management. /// /// Array of FieldInfo for fields of type ITrackingBehaviourEvent public FieldInfo[] GetTrackingBehaviourEvents() { return GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) .Where(f => typeof(ITrackingBehaviourEvent).IsAssignableFrom(f.FieldType)) .ToArray(); } /// /// Filters an array of objects to only include those of the specified type. /// /// Type to filter for /// Array of objects to filter /// Array of objects of type TS protected TS[] Select(Object[] objects) where TS : Object => objects .Where(o => o.GetType() == typeof(TS) || o.GetType().IsSubclassOf(typeof(TS))) .Select(o => o as TS).ToArray(); /// /// Gets the first object of the specified type from an array of objects. /// /// Type to find /// Array of objects to search /// The first object of type TS, or null if none exists protected TS First(Object[] objects) where TS : Object => (TS)objects .FirstOrDefault(o => o.GetType() == typeof(TS) || o.GetType().IsSubclassOf(typeof(TS))); } }