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)));
}
}